package AFX::ARF;

=head1 NAME

AFX::ARF - ARF Processing

  add_argument()             - Add argument
  add_error()                - Add ARF error
  add_machine()              - Add machine
  add_machine_passwd()       - Add password into machine
  add_machine_template()     - Add validated template into machine
  add_session()              - Add session
  add_step()                 - Add step
  add_step_command()         - Add command into step
  add_step_hint()            - Add hint into step
  add_step_static()          - Add static into step
  add_title()                - Add title
  check_node_argument()      - Check argument for node in step
  check_node_param()         - Check parameter in step
  check_step_static()        - Check static by parameter
  compare_param()            - Compare parameters between nodes
  create()                   - Create ARF
  decrypt_passwd()           - Return decrypted passwords for login
  DESTROY()                  - ARF destructor
  execute_refer()            - Execute Step's Referrer
  execute_step()             - Execute step for node
  generate()                 - Generate ARF description
  get_argument()             - Return argument for argument identifier
  get_audit_end()            - Return ARF end-time-date
  get_audit_start()          - Return ARF start-time-date
  get_id()                   - Return ARF Identifier
  get_list_machine()         - Return list of machines
  get_list_session()         - Return list of sessions
  get_machine()              - Return ASF machine for specified node's Identifier
  get_machine_id()           - Return ARF Machine's Identifier
  get_machine_node()         - Return Machine's node for node's Identifier
  get_machine_passwd()       - Return Machine's password for login
  get_machine_root()         - Get Machine's root using defaults from ACF
  get_machine_template()     - Return Machine's template for template's Identifier
  get_machine_unit()         - Return ARF Machine's Unit
  get_node()                 - Return Machine's node for node's Identifier
  get_node_id()              - Return Initial Node's identifier
  get_node_prompt()          - Return Node's prompt for login
  get_session()              - Return session
  get_site()                 - Return ARF's site
  get_statistics()           - Return ARF statistics
  get_step()                 - Return step for step id and template id
  get_step_argument()        - Return Step's argument
  get_step_command()         - Return Step's command
  get_step_hint()            - Return Step's hint for Parameter's Identifier or command
  get_step_param()           - Return Step's parameters for Parameter's Identifier
  get_step_static()          - Return Step's static for Node's Identifier
  get_title()                - Return title
  get_version()              - Return ARF Version
  mastering_machine()        - Mastering Machine using defaults from ACF
  message_argument_added()   - Message: Argument added into machine in ASF.
  message_created()          - Message: ARF created
  message_critical()         - Message: Critical issue(s) detected
  message_failed_open()      - Message: ARF failed opening
  message_failed_session()   - Message: Session closing failed
  message_failed_valid()     - Message: ARF failed validation
  message_invalid_argument() - Message: Argument has invalid content
  message_invalid_machine()  - Message: Invalid machine specified
  message_invalid_param()    - Message: Parameter has invalid content
  message_invalid_passwd()   - Message: Invalid password specified.
  message_invalid_refer()    - Message: Referrer should be specified for node
  message_invalid_step()     - Message: Invalid step specified
  message_no_hint()          - Message: Command or Param within Step has no hint
  message_no_machine()       - Message: No machine found
  message_no_param()         - Message: Hint within Step has no corresponding param
  message_no_step()          - Message: No steps executed for machine
  message_param_summary()    - Message: ARF formatting parameters output
  message_passwd_added()     - Message: Password added in ASF.
  message_passwd_encrypted() - Message: Password for unit as login encrypted
  message_processed()        - Message: Finished machine
  message_processing()       - Message: ARF processing
  message_step_processing()  - Message: Template's Step Processing for Machine
  message_template_added()   - Message: Template added in ASF.
  message_template_removed() - Message: Template removed in ASF.
  new()                      - ARF contructor
  open()                     - Open ARF
  remove_session()           - Remove session
  update()                   - Update ARF

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2010 Mikhail Volkov

This file is part of AuditFX.

    AuditFX is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    AuditFX is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with AuditFX.  If not, see <http://www.gnu.org/licenses/>.
=cut

use strict;
use Carp;
use Exporter ();

use vars qw($VERSION);
my @ISA = qw(Exporter);
$VERSION = '1.0';

##############################################################################
# Globals
#
use POSIX qw(strftime);
use Storable qw(dclone);
use FileHandle;
use Algorithm::Diff;

use AFX::ATF;
use AFX::Session;

##############################################################################
#
# add_argument()
#
# Add argument
#
# Return:
# - 1          - if argument added
# - 0          - otherwise
#
# Arguments expected are:
# - self       - Object reference
# - argument   - Argument's Hash
# - template_id  - Template's Id

sub add_argument {
    my ( $self, $argument, $template_id ) = @_;

## Return error, if argument already exists
    return 0 if $self->get_argument( $template_id, $argument->{id} );

## Set Title's template
    $argument->{template} = $template_id if $template_id;

## Add argument into ARF
    push( @{ $self->{_xml}->{argument} }, $argument );

## Set updated state
    $self->{updated}++;

## Return Ok
    return 1;

} ## end sub add_argument

##############################################################################
#
# add_error()
#
# Add ARF error
#
# Return:
# - 1          - if error message added
# - 0          - otherwise
#
# Arguments expected are:
# - self       - Object reference
# - errmsg     - Error Message

sub add_error {
    my ( $self, $errmsg ) = @_;

## Check error exists in ARF
    foreach my $error ( @{ $self->{_xml}->{error} } ) {

## Return error if error already added
        return 0 if $error->{content} eq $errmsg;
    }

## Add error into ARF
    push( @{ $self->{_xml}->{error} }, { content => $errmsg } );

## Set updated state
    $self->{updated}++;

## Return Ok
    return 1;

} ## end sub add_error

##############################################################################
#
# add_machine()
#
# Add machine
#
# Return:
# - 1          - if machine added
# - 0          - otherwise
#
# Arguments expected are:
# - self       - Object reference
# - machine    - Machine's Hash
# - node_id    - Node's Identifier
# - remote     - Remote machine if 1

sub add_machine {
    my ( $self, $machine, $node_id, $remote ) = @_;

## Return error, if machine already exists
    return 0 if $self->get_machine($node_id);

## Add machine into ARF
    push( @{ $self->{_xml}->{machine} }, dclone($machine) );

## Get Machine
    my $arf_machine = $self->get_machine($node_id);

## Set machine as remote if defined
    $arf_machine->{remote} = "true" if $arf_machine and $remote;

## Set updated state
    $self->{updated}++;

## Mastering Machine
    $self->mastering_machine($node_id);

## Return Ok
    return 1;

} ## end sub add_machine

##############################################################################
#
# add_machine_passwd()
#
# Add password into machine
#
# Return:
# - 1                 - if added sucessfully
# - 0                 - otherwise
#
# Arguments expected are:
# - self              - Object reference
# - node_id           - Node's Identifier
# - passwd            - Password hash

sub add_machine_passwd {
    my ( $self, $node_id, $passwd ) = @_;

## Get Machine
    my $machine = $self->get_machine($node_id);

## Return Error, if no machine found
    return 0 if !$machine;

## Return Error, if passwd already added
    return 0 if $self->get_machine_passwd( $node_id, $passwd->{login} );

## Print Passwd added message
    $self->message_passwd_added( $passwd->{login} );

## Add Passwd into machine
    push( @{ $machine->{passwd} }, $passwd );

## Set updated state
    $self->{updated}++;

## Set machine updated flag
    $self->{machine_updated}++;

## Return Ok
    return 1;

} ## end sub add_machine_passwd

##############################################################################
#
# add_machine_template()
#
# Add validated template into machine
#
# Return:
# - 1                 - if added sucessfully
# - 0                 - otherwise
#
# Arguments expected are:
# - self              - Object reference
# - node_id           - Node's Identifier
# - template_id       - Template's Id
# - template_type     - Template's Type
# - template_version  - Template's Version

sub add_machine_template {
    my ( $self, $node_id, $template_id, $template_type, $template_version ) =
      @_;

## Get Machine
    my $machine = $self->get_machine($node_id);

## Return Error, if no machine found
    return 0 if !$machine;

## Check template
    my $template = $self->get_machine_template( $node_id, $template_id );

## If template already added
    if ($template) {

## Set valid true
        $template->{valid} = "true";

## Return Error
        return 0;
    }

## Print Template Added message
    $self->message_template_added( $template_id, $machine->{id} );

## Add ATF into machine
    push(
        @{ $machine->{template} },
        {
            id      => $template_id,
            type    => $template_type,
            version => $template_version,
            valid   => "true"
        }
    );

## Set updated state
    $self->{updated}++;

## Set machine updated flag
    $self->{machine_updated}++;

## Return Ok
    return 1;

} ## end sub add_machine_template

##############################################################################
#
# add_session()
#
# Add session
#
# Return:
# - 1          - if session added
# - 0          - otherwise
#
# Arguments expected are:
# - self       - Object reference
# - session    - Session Object

sub add_session {
    my ( $self, $session ) = @_;

## Return error, if session invalid
    return 0 if !$session or !$session->{id};

## Return Error, if session already exist
    return 0 if $self->get_session( $session->{id} );

## Add session
    push( @{ $self->{_sessions} }, $session );

## Return Ok
    return 1;

} ## end sub add_session

##############################################################################
#
# add_step()
#
# Add step
#
# Return: step Hash
#
# Arguments expected are:
# - self        - Object reference
# - step_id     - Step's Identifier
# - login       - Step's command login
# - cmd         - Step's command
# - end         - End prompt

sub add_step {
    my ( $self, $step_id, $login, $cmd, $end ) = @_;

## Return error, if step already exists
    return 0 if $self->get_step($step_id);

## Set step
    my $step = { id => $step_id };

## Set command
    $self->add_step_command( $step, $login, $cmd, $end );

## Add step into ARF
    push( @{ $self->{_xml}->{step} }, $step );

## Set updated state
    $self->{updated}++;

## Return Ok
    return $step;

} ## end sub add_step

##############################################################################
#
# add_step_command()
#
# Add command into step
#
# Return:
# - 1            - if added sucessfully
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - step         - Step Hash
# - login        - Step's command login
# - cmd          - Step's command
# - end          - End prompt

sub add_step_command {
    my ( $self, $step, $login, $cmd, $end ) = @_;

## Set command
    my $command = { login => $login, content => $cmd };

## Set command's end, if specified
    $command->{end} = $end if $end;

## Add command into step
    push( @{ $step->{command} }, $command );

## Set updated state
    $self->{updated}++;

## Return Ok
    return 1;

} ## end sub add_step_command

##############################################################################
#
# add_step_hint()
#
# Add hint into step
#
# Return:
# - 1            - if added sucessfully
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - step         - Step Hash
# - node_id      - Node's Identifier
# - remote_id    - Remote Node's Identifier
# - param_id     - Parameter's Identifier
# - content      - Hint's content

sub add_step_hint {
    my ( $self, $step, $node_id, $remote_id, $param_id, $content ) = @_;

## Set hint
    my $hint = { content => $content };

## Add hint's parameter id
    $hint->{id} = "cmp_" . $param_id . "_" . $node_id . "_" . $remote_id
      if $param_id;

## Add static into step
    push( @{ $step->{hint} }, $hint );

## Set updated state
    $self->{updated}++;

## Return Ok
    return 1;

} ## end sub add_step_hint

##############################################################################
#
# add_step_static()
#
# Add static into step
#
# Return: static Hash
#
# Arguments expected are:
# - self         - Object reference
# - step         - Step Hash
# - node_id      - Node's Identifier
# - remote_id    - Remote Node's Identifier
# - content      - Static's content
# - ref_id       - Referrer's Identifier
# - param_id     - Parameter's Identifier

sub add_step_static {
    my ( $self, $step, $node_id, $remote_id, $content, $ref_id, $param_id ) =
      @_;

## Set static
    my $static = {
        content => $content,
        node    => $node_id,
    };

## Set static's remote
    $static->{rnode} = $remote_id if $remote_id;

## Set static's referrer
    $static->{ref} = $ref_id if $ref_id;

## Set static's id
    $static->{id} = $param_id if $param_id;

## Add static into step
    push( @{ $step->{static} }, $static );

## Set updated state
    $self->{updated}++;

## Return Ok
    return $static;

} ## end sub add_step_static

##############################################################################
#
# add_title()
#
# Add title
#
# Return:
# - 1            - if title added
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - title        - Title's Hash
# - template_id  - Template's Id

sub add_title {
    my ( $self, $title, $template_id ) = @_;

## Return error, if title already exists
    return 0 if $self->get_title( $template_id, $title->{step}, $title->{id} );

## Set Title's template
    $title->{template} = $template_id if $template_id;

## Add title into ARF
    push( @{ $self->{_xml}->{title} }, $title );

## Set updated state
    $self->{updated}++;

## Return Ok
    return 1;

} ## end sub add_title

##############################################################################
#
# check_node_argument()
#
# Check argument for node in step
#
# Return:
# - Argument hash   - if it checked
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object reference
# - step            - Step Hash
# - argument_id     - Argument's Identifier
# - node_id         - Node's Identifier
# - remote_id       - Remote Node's Identifier

sub check_node_argument {
    my ( $self, $step, $argument_id, $node_id, $remote_id ) = @_;

## Check argument in step
    foreach my $argument ( @{ $step->{argument} } ) {

## Skip argument with specified node or wrong argument
        next
          if $argument->{node} ne $node_id
              or $argument->{rnode} ne $remote_id
              or $argument->{id} ne $argument_id;

## Return Argument Hash
        return $argument;
    }

## Get step's argument from step
    my $argument =
      $self->get_step_argument( $step, $argument_id, $node_id, $remote_id );

## if Argument not available, create new
    $argument = {
        id      => $argument_id,
        content => ""
    } if !$argument;

## Set argument's node
    $argument->{node} = $node_id;

## Set argument's node
    $argument->{rnode} = $remote_id if $remote_id;

## Add argument into step
    push( @{ $step->{argument} }, $argument );

## Set updated state
    $self->{updated}++;

## Return Argument hash
    return $argument;

} ## end sub check_node_argument

##############################################################################
#
# check_node_param()
#
# Check parameter for node in step
#
# Return:
# - Param hash   - if it checked
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - step         - Step Hash
# - param_id     - Parameter's Identifier
# - node_id      - Node's Identifier
# - remote_id    - Remote Node's Identifier

sub check_node_param {
    my ( $self, $step, $param_id, $node_id, $remote_id ) = @_;

## Check parameters in step
    foreach my $param ( @{ $step->{param} } ) {

## Skip parameters with specified node or wrong parameter
        next
          if $param->{node} ne $node_id
              or $param->{rnode} ne $remote_id
              or $param->{id} ne $param_id;

## Return Param Hash
        return $param;
    }

## Get step's parameter from step
    my $param = $self->get_step_param( $step, $param_id, $node_id, $remote_id );

## Exception: Invalid Step
    if ( !$param ) {

## Add error into ARF
        $self->message_invalid_param( $node_id, $remote_id, $param_id,
            $step->{id} );

## Return error
        return 0;
    }    ## Exception

## Set parameter's node
    $param->{node} = $node_id;

## Set severity as major, if not defined
    $param->{severity} = 'major' if !$param->{severity};

## Set parameter's node
    $param->{rnode} = $remote_id if $remote_id;

## Get step's static
    my $static = $self->get_step_static( $step, $node_id, $remote_id );

## Set default static for parameter
    my $param_static = "";

## If static not found
    if ( !$static ) {

## Set error value
        $param->{value} = 0;
    }
    else {

## Make a copy of static
        my $static_copy = $static->{content};

## Remove many spaces
        $static_copy =~ s/\x20+/ /g;

## Check static and get parameter's value
        ( $param->{value}, $param_static ) = eval {
            $self->check_step_static( $static_copy, $param->{method},
                $param->{content} );
        };

## If error within Regex occured
        if ($@) {

## Add message into ARF
            $self->message_invalid_step( $step->{id}, $step->{template} );

## Set N/A values
            $param->{value} = 2;
        }
    }

## Add parameter into step
    push( @{ $step->{param} }, $param );

## Set updated state
    $self->{updated}++;

## Return Param hash if static is empty
    return $param if !defined($param_static) or $param_static eq "";

## Add parameter's static into step
    $self->add_step_static( $step, $param->{node}, $param->{rnode},
        $param_static, "", $param->{id} );

## Check parameter's value
    if ( $param->{check} ) {

## Get argument from ARF
        my $argument =
          $self->check_node_argument( $step, $param->{check}, $node_id,
            $remote_id );

## Check if argument type is passwd and nopasswd not specified
        if (   !$argument
            or !$argument->{content}
            or $param->{value} != 1
            or $param_static eq $argument->{content} )
        {

## Remove parameter's checking
            delete( $param->{check} );

## Return parameter's
            return $param;
        }

## Set parameter as false
        $param->{value} = 0;

## Change arguments to it's content
        $param->{check} = $argument->{content};
    }

## Return parameter's
    return $param;

} ## end sub check_node_param

##############################################################################
#
# check_step_static()
#
# Check step's static by parameter
#
# Return:
# - (2, "")           - if N/A
# - (1, $1 matching)  - if Ok for regex (=~, !~) methods
# - (0, "")           - if Error
#
# Arguments expected are:
# - self              - Object reference
# - static            - Static for checking
# - method            - Method to be used for validation
# - rule              - Validation rule

sub check_step_static {
    my ( $self, $static, $method, $rule ) = @_;

## Return Not Applicable
    return ( 2, "" ) if $static =~ /^N\/A/;

## If rule not specified, return error
    return ( 0, "" ) if !$rule;

## Regex match
    if ( $method eq "=~" ) {

## If Static is Ok for rule, return Ok
        return ( 1, $1 ) if $static =~ /$rule/;
    }

## Regex not match
    if ( $method eq "!~" ) {

## If Static is not Ok for rule, return Ok
        return ( 1, $1 ) if $static !~ /$rule/;
    }

## Invalid method specified, return Error
    return ( 0, "" );

} ## end sub check_step_static

##############################################################################
#
# compare_param()
#
# Compare parameters between nodes
#
# Return:
# - 1         - if compared
# - 0         - otherwise
#
# Arguments expected are:
# - self      - Object reference
# - multi     - if 1, then compare multimachines

sub compare_param {
    my ( $self, $multi ) = @_;

## Set default multi to No
    $multi = 0 if !$multi;

## Compare parameters in steps
    foreach my $step ( @{ $self->{_xml}->{step} } ) {

## Check comparing parameters in Step
        foreach my $param ( @{ $step->{param} } ) {

## Skip wrong parameter
            next
              if !$param->{compare}
                  or !$param->{node}
                  or $param->{value} == 2;

## Get static for parameter
            my $param_static =
              $self->get_step_static( $step, $param->{node}, $param->{rnode},
                $param->{id} );

## If no static found
            if ( !$param_static or $param_static->{content} eq '' ) {

## Set error value if result not equal
                $param->{value} = 0;

## Goto next parameter
                next;
            }

## Define new hint
            my $hint = "";

## Comparing between nodes
            if ( $param->{compare} eq "node" ) {

## Compare results for all node
                foreach my $static ( @{ $step->{static} } ) {

## Skip static with non-equal id
                    next
                      if $static->{id} ne $param->{id}
                          or (    $static->{node} eq $param->{node}
                              and $static->{rnode} eq $param->{rnode} );

## Skip difference machine nodes
                    next
                      if !$multi
                          and $self->get_machine_id( $static->{node} ) ne
                          $self->get_machine_id( $param->{node} );

## If static has no content
                    if ( $static->{content} eq '' ) {

## Set error value
                        $param->{value} = 0;

## Goto next static
                        next;
                    }

## Goto next node if parameters equal
                    next if $param_static->{content} eq $static->{content};

## Get sequences from static
                    my @seq1 = split( "\n", $param_static->{content} );
                    my @seq2 = split( "\n", $static->{content} );

## Create new Diff object
                    my $diff = Algorithm::Diff->new( \@seq1, \@seq2 );

## Return line numbers, not indices
                    $diff->Base(1);

## Check all lines
                    while ( $diff->Next() ) {

## Skip if lines equal
                        next if $diff->Same();

## Define hint nodes for parameter
                        my $hint_param = $param->{node};
                        $hint_param .= "&" . $param->{rnode} if $param->{rnode};

## Define hint nodes for static
                        my $hint_static = $static->{node};
                        $hint_static .= "&" . $static->{rnode}
                          if $static->{rnode};

## Add hint with difference
                        $hint .= "\n---";
                        $hint .= "\n$hint_param< $_" for $diff->Items(1);
                        $hint .= "\n$hint_static> $_" for $diff->Items(2);
                    }

## Set error value if result not equal
                    $param->{value} = 0;
                }    ## static processing
            }    ## node comparing

## Add hint into step
            $self->add_step_hint( $step, $param->{node}, $param->{rnode},
                $param->{id}, $hint )
              if $hint;
        }    ## parameters processing
    }    ## step processing

## Return Ok
    return 1;

} ## end sub compare_param

##############################################################################
#
# create()
#
# Create ARF
#
# Return:
# - 1         - if created sucessfully
# - 0         - otherwise
#
# Arguments expected are:
# - self      - Object reference

sub create {
    my ($self) = @_;

## If no steps executed for machine
    if ( !@{ $self->{_xml}->{step} } ) {

## Add message into ARF
        $self->message_no_step();
    }

## Set end-time-date
    $self->{_xml}->{end} = time();

## Save ARF
    eval {
        $self->{Process}->{ACF}->{xs}->XMLout(
            $self->{_xml},
            RootName   => 'audit',
            OutputFile => $self->{path}
        );
    };

## Validate ARF
    my $error = $self->{Process}->{ACF}->validate_file( $self->{path} );

## Exit if ARF failed validation
    if ($error) {

## Print failed validation error
        $self->message_failed_valid($error);

## Return Error
        return 0;
    }

## Print ARF created message
    $self->message_created() if $self->{Process}->is_debug();

## Generate description
    $self->generate("");

## Return Ok
    return 1;

} ## end sub create

##############################################################################
#
# decrypt_passwd()
#
# Return decrypted passwords for login
#
# Return:
# - Passwd hash  - if it exists
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - node_id      - Node's Identifier
# - login        - Password's Login

sub decrypt_passwd {
    my ( $self, $node_id, $login ) = @_;

## Get Machine's password for login
    my $passwd = $self->get_machine_passwd( $node_id, $login );

## If passwd not specified in Machine, return Error
    return 0 if !$passwd;

## Create copy of passwd
    my $passwd_copy = {
        login   => $passwd->{login},
        content => $passwd->{content}
    };

## Set additional attributes of passwd
    $passwd_copy->{encrypted} = $passwd->{encrypted}
      if $passwd->{encrypted};
    $passwd_copy->{unit} = $passwd->{unit} if $passwd->{unit};

## Open Keyfile
    my $cbc = $self->{Process}->{ACF}->open_keyfile();

## If password encrypted
    if (    $cbc
        and $passwd_copy->{encrypted}
        and $passwd_copy->{encrypted} eq 'true'
        and $passwd_copy->{content} )
    {
## Decrypt password and set encrypted flag to false
        $passwd_copy->{content} =
          eval { $cbc->decrypt_hex( $passwd_copy->{content} ); };

## Set encrypted state as false
        $passwd_copy->{encrypted} = 'false';
    }

## Return Passwd hash
    return $passwd_copy;

} ## end sub decrypt_passwd

##############################################################################
#
# DESTROY()
#
# ARF destructor
#
# Arguments expected are:
# - self       - Object reference

sub DESTROY {
    my ($self) = @_;

## Print debug message
    print "DEBUG::Object ARF " . $self->{id} . " destroyed\n"
      if $self->{Process} and $self->{Process}->is_debug();

## Check opened sessions
    foreach my $session ( @{ $self->{_sessions} } ) {

## Skip wrong sessions
        next if !$session or !$session->{id};

## Close session
        $session->close_session();
    }

## If all session closed, return Ok
    return 1 if !$self->{_sessions} and !@{ $self->{_sessions} };

## Check opened sessions
    foreach my $session ( @{ $self->{_sessions} } ) {

## Skip wrong sessions
        next if !$session->{id};

## Add error into ARF
        $self->message_failed_session( $session->{id} );
    }

## Return Ok
    return 1;

} ## end sub DESTROY

##############################################################################
#
# execute_refer()
#
# Execute Step's Referrer
#
# Return:
# - (Step hash, Param hash)  - if it exists
# - (0, 0)                   - otherwise
#
# Arguments expected are:
# - self                     - Object reference
# - refer                    - Referrer Hash
# - node_id                  - Node's Id
# - remote_id                - Remote's Id

sub execute_refer {
    my ( $self, $refer, $node_id, $remote_id ) = @_;

## Check step in ARF
    my $arf_step = $self->get_step( $refer->{step}, $refer->{template} );

## If step exists in ARF
    if ( $arf_step and $arf_step->{id} ) {

## Execute step for node
        $self->execute_step( $arf_step, $node_id, $remote_id );

## Get step's static
        my $static = $self->get_step_static( $arf_step, $node_id, $remote_id );

## Check is referrer's step already checked
        if ($static) {

## Get parameter in step
            my $arf_param = $self->check_node_param(
                $arf_step,       $refer->{content},
                $static->{node}, $static->{rnode}
            );

## Return referrer's step if already checked
            return ( $arf_step, $arf_param )
              if $arf_param and $arf_param->{id};
        }
    }

## Get Machine
    my $machine = $self->get_machine($node_id);

## Return Error, if no machine found
    return ( 0, 0 ) if !$machine;

## Get Node
    my $node = $self->get_node($node_id);

## Return Error, if no node found
    return ( 0, 0 ) if !$node;

## Define default templata dir
    my $atfdir = 'template';

## If scenario executed and referrer match scenario
    $atfdir = 'scenario' if $refer->{template} eq $self->{scenario};

## Open ATF
    my $atf = new AFX::ATF( $self, $refer->{template}, $atfdir );

## Return Error if template not valid for machine
    return ( 0, 0 ) if !$atf or !$atf->validate($node_id);

## Get parameter's step in ATF
    my $step = $atf->get_step( $refer->{step} );

## If invalid step
    if ( !$step or !$step->{id} ) {

## Add error into ARF
        $self->message_invalid_step( $refer->{step}, $refer->{template} );

## Return Error
        return ( 0, 0 );
    }

## Set Step template if it not specified in ATF
    $step->{template} = $atf->get_id();

## Execute step for node
    my $refer_step = $self->execute_step( $step, $node_id, $remote_id );

## If referrer's step not executed
    if ( !$refer_step or !$refer_step->{id} ) {

## Return error
        return ( 0, 0 );
    }

## Get parameter in step
    my $refer_param =
      $self->check_node_param( $refer_step, $refer->{content}, $node_id,
        $remote_id );

## Return referrer step
    return ( $refer_step, $refer_param );

} ## end sub execute_refer

##############################################################################
#
# execute_step()
#
# Execute step for node
#
# Return:
# - Step hash    - if it executed
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - step         - Step Hash
# - node_id      - Node's Identifier
# - remote_id    - Remote Node's Identifier

sub execute_step {
    my ( $self, $step, $node_id, $remote_id ) = @_;

## Get Node
    my $local_node = $self->get_node($node_id);

## Return Error, if no node found
    return 0 if !$local_node;

## Get Remote Node
    my $remote_node = $self->get_node($remote_id);

## Get Node's IP
    my $remote_ip =
      $self->check_node_argument( $step, "#REMOTE_IP#", $node_id, $remote_id );

## If Remote IP specified
    if ( $remote_ip and $remote_node and $remote_node->{ip} ) {

## Set Remote node's IP
        $remote_ip->{content} = $remote_node->{ip};
    }

## If remote node's IP from step not specified
    if (   !$remote_id
        and $remote_ip
        and $remote_ip->{unit}
        and !$remote_ip->{step} )
    {

## Exception: No machine found in ASF
        if ( !$self->{Process}->{ASF}->get_list_machine() ) {

## Add step's static into ARF step
            $self->message_no_machine( $step->{id}, $remote_ip->{unit} );

## Return Error
            return 0;
        }    ## Exception

## Check machines, which will be processed
        foreach
          my $asf_machine ( @{ $self->{Process}->{ASF}->get_list_machine() } )
        {

## Goto next if wrong machine unit
            next if $asf_machine->{unit} ne $remote_ip->{unit};

## Goto next if wrong machine
            next
              if $remote_ip->{content}
                  and $asf_machine->{id} !~ $remote_ip->{content};

## Check node in Machine
            foreach my $asf_node ( @{ $asf_machine->{node} } ) {

## Goto next node if node's IP empty
                next if !$asf_node->{ip};

## Define as remote
                my $remote = 1;

## Add machine into ARF
                $self->add_machine( $asf_machine, $asf_node->{host}, $remote );

## Execute step for remote node
                $self->execute_step( $step, $node_id, $asf_node->{host} );
            }
        }

## Return Ok
        return $step;
    }

## If step's unit specified
    if (   !$remote_id
        and $remote_ip
        and $remote_ip->{unit}
        and $remote_ip->{step}
        and $remote_ip->{content} )
    {

## Get machine from ASF by IP
        my $asf_machine =
          $self->{Process}->{ASF}
          ->get_machine_by_node_ip( $remote_ip->{content}, $remote_ip->{unit} );

## Exception: Invalid remote machine
        if ( !$asf_machine or !$asf_machine->{node} ) {

## Add step's static into ARF step
            $self->message_invalid_machine( $remote_ip->{content},
                $remote_ip->{unit} );

## Return Error
            return 0;
        }    ## Exception

## Get remote node from ASF
        my $asf_node =
          $self->{Process}->{ASF}
          ->get_machine_node_by_ip( $asf_machine, $remote_ip->{content} );

## Define as remote
        my $remote = 1;

## Add machine into ARF
        $self->add_machine( $asf_machine, $asf_node->{host}, $remote );

## Set REMOTE_IP remote node
        $remote_ip->{rnode} = $asf_node->{host};

## Execute step for remote node
        return $self->execute_step( $step, $node_id, $asf_node->{host} );
    }

## Check step exists in ARF
    my $arf_step = $self->get_step( $step->{id}, $step->{template} );

## If step doesn't exist
    if ( !$arf_step ) {

## Add step into ARF
        push( @{ $self->{_xml}->{step} }, $step );

## Get step from ARF
        $arf_step = $self->get_step( $step->{id}, $step->{template} );
    }

## Get step's static
    my $static = $self->get_step_static( $arf_step, $node_id, $remote_id );

## If static already in Step
    return $arf_step if $static;

## Define referrer identifier
    my $ref_id = "";

## Check parameters for template validation
    foreach my $param ( @{ $arf_step->{param} } ) {

## If referrer ATF specified
        next if !$param->{template};

## Set command's node as local
        my $param_node = $node_id;

## Set command's node as remote
        $param_node = $remote_id if $remote_id and $remote_node;

## Get Machine
        my $param_machine = $self->get_machine($param_node);

## Check template
        my $template =
          $self->get_machine_template( $param_node, $param->{template} );

## Goto next parameter if template already validated
        next
          if $template
              and $template->{valid}
              and $template->{valid} eq 'true';

## Open ATF
        my $atf = new AFX::ATF( $self, $param->{template}, 'template' );

## Goto next parameter if ATF valid
        next if $atf and $atf->validate($param_node);

## Add step's static into ARF step
        $self->add_step_static( $arf_step, $node_id, $remote_id, "N/A",
            $ref_id );

## Return Error if ATF not valid
        return 0;
    }

## Get step's command from step
    my $step_cmd = $self->get_step_command( $arf_step, $node_id, $remote_id );

## Exception: Invalid Step
    if ( !$step_cmd or !$step_cmd->{content} ) {

## Add step's static into ARF step
        $self->add_step_static(
            $arf_step,
            $node_id,
            $remote_id,
            $self->message_invalid_step(
                $arf_step->{id}, $arf_step->{template}
            ),
            $ref_id
        );

## Return Error
        return 0;
    }    ## Exception

## Set session node as local
    my $session_node = $local_node;

## Set session node as remote if specified
    $session_node = $remote_node
      if $remote_id
          and $remote_node
          and $step_cmd->{remote}
          and $step_cmd->{remote} =~ /(true|static)/;

## Create new session
    my $session = new AFX::Session( $self, $step_cmd->{login}, $session_node );

## Exception: No connection available to host:terminal as login
    if ( !$session ) {

## Add step's static into ARF step
        $self->add_step_static( $arf_step, $node_id, $remote_id,
            $self->message_failed_session( $session->{id} ), $ref_id );

## Return Error
        return 0;
    }    ## Exception

## Define static variables
    my $static_content = "N/A";

## Define static's start
    my $static_start = time();

## Check step's referrer
    if ( !$step->{refer} ) {

## Execute command in session
        eval { $static_content = $session->execute( $step_cmd, $step->{id} ); };

## If error within Session occured
        $static_content = "$@" if $@;
    }
    else {

## Define referrer valid
        my $ref_valid = 0;

## Check referrer for machine
        foreach my $refer ( @{ $arf_step->{refer} } ) {

## Set referrer nodes as local
            my $ref_node_id   = $node_id;
            my $ref_remote_id = $remote_id;

## Change referrer nodes as remote
            if ( $refer->{remote} and $refer->{remote} eq 'true' ) {
                $ref_node_id   = $remote_id;
                $ref_remote_id = $node_id;
            }

## Execute Referrer
            my ( $refer_step, $refer_param ) =
              $self->execute_refer( $refer, $ref_node_id, $ref_remote_id );

## If referrer specified, but not referrer's step found
            if ( !$refer_step ) {

## Add error into ARF
                $self->message_invalid_refer( $ref_node_id, $ref_remote_id,
                    $refer->{step} );

## Goto next referrer
                next;
            }

## Add referrer step
            $ref_id =
                $refer_step->{template} . ":"
              . $refer_step->{id} . ":"
              . $refer_param->{id}
              if $refer_param;

## If referrer specified and valid
            if ( !$refer_param or $refer_param->{value} != 1 ) {

## Clear referrer's id
                $ref_valid = 0;

## Goto next referrer
                last;
            }

## Increment valid status
            $ref_valid++;
        }

## If referrer specified and valid
        if ( $ref_valid and $ref_id ) {

## Set static's start
            $static_start = time();

## Execute command in session
            eval {
                $static_content = $session->execute( $step_cmd, $step->{id} );
            };

## If error within Session occured
            $static_content = "$@" if $@;
        }
    }

## Add step's static into ARF step
    $static =
      $self->add_step_static( $arf_step, $node_id, $remote_id, $static_content,
        $ref_id );

## Set static's time
    my $static_time = time() - $static_start;
    $static->{time} = $static_time if $static_time > 10;

## Check parameters
    foreach my $param ( @{ $arf_step->{param} } ) {

## If referrer ATF specified
        next if $param->{template};

## Check parameter in step
        $self->check_node_param( $arf_step, $param->{id}, $node_id,
            $remote_id );
    }

## Return ARF's step
    return $arf_step;

} ## end sub execute_step

##############################################################################
#
# generate()
#
# Generate ARF description
#
# Return:
# - html or text - if requested
# - 1            - if description generated
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - request      - 'html' for HTML, 'txt' for Text

sub generate {
    my ( $self, $request ) = @_;

## Define CSS content
    my $content = "";

## Read CSS from file
    if ( open( CSS, "< lib/AFX.css" ) ) {
        $content = join( '', <CSS> );
        close(CSS);
        chomp($content);
    }

## Define txt
    my $txt          = $self->{Process}->{ACF}->message_title();
    my $template_txt = "";

## Define node's identifier
    my $node_id = $self->get_node_id();

## Get initial machine's Identifier and Unit
    my $machine_id   = $self->get_machine_id($node_id);
    my $machine_unit = $self->get_machine_unit($node_id);

## Define HTML hash
    my $html = {
        head => {
            style => {
                type    => "text/css",
                content => $content
            },
            title => {
                content => "ARF $machine_id [$machine_unit] from "
                  . $self->get_site()
            }
        },
        body => {}
    };

## Define HTML's report hash
    my $report = {
        head => {
            style => {
                type    => "text/css",
                content => $content
            },
            title => {
                content => "ARR $machine_id [$machine_unit] from "
                  . $self->get_site()
            }
        },
        body => {}
      }
      if $self->{report};

## Add version
    push(
        @{ $html->{body}->{p} },
        {
            class   => "title",
            content => $self->{Process}->{ACF}->message_title()
        }
    );
    push(
        @{ $report->{body}->{p} },
        {
            class   => "title",
            content => $self->{Process}->{ACF}->message_title()
        }
    ) if $self->{report};

## Add TOP for linking
    push( @{ $html->{body}->{p} }, { a => { name => "top" } } );

## Add Site Identifier in HTML
    push(
        @{ $html->{body}->{p} },
        { class => "site", content => $self->get_site() }
    );
    push(
        @{ $report->{body}->{p} },
        { class => "site", content => $self->get_site() }
    ) if $self->{report};

## Add Site Identifier in Text
    $txt .= "\n\nSite: " . $self->get_site() . "\n";

## Define arf start date
    my $arf_date = localtime( $self->get_audit_start() );

## Get time
    my $arf_takes = $self->get_audit_end() - $self->get_audit_start();

## Format machine and unit for output
    $machine_unit = "$machine_id [$machine_unit] on $arf_date ";
    $machine_unit .= "[execution time: $arf_takes sec.]" if $arf_takes > 0;

## Add Machine Identifier in HTML
    push(
        @{ $html->{body}->{p} },
        { class => "machine", content => $machine_unit }
    );
    push(
        @{ $report->{body}->{p} },
        { class => "machine", content => $machine_unit }
    ) if $self->{report};

## Add Machine as Text
    $txt .= "\n$machine_unit\n";

## Get ARF statistics
    my ( $arf_param, $failed_atf ) = $self->get_statistics();

## Define variables for table with templates
    my $template_error  = "";
    my $template_review = "";
    my $template_ok     = "";

## Define variables for mastering
    my ( %mastered, %nonmastered );

## Check mastered/non-mastered templates/steps in ARF
    foreach my $step ( @{ $self->{_xml}->{step} } ) {

## Skip non-reviewed steps
        next if $step->{review} and $step->{review} eq 'false';

## Skip steps not for this machine
        next if !$self->get_step_static( $step, $node_id );

## Format Step title
        my $template_step = $step->{template} . ":" . $step->{id};

## Skip already showed mastered template
        next if $mastered{ $step->{template} };

## If step's template not mastered
        if ( !$self->get_machine_template( $node_id, $step->{template} ) ) {

## Set param errors in step flag
            my $param_error_step = 0;

## Check all parameters
            foreach my $param ( @{ $step->{param} } ) {

## Skip parameter if it's from ATF
                next if !$param->{node};

## Skip referrer parameter
                next if $param->{severity} eq 'refer';

## Set error flag if parameter value is error
                $param_error_step++ if !$param->{value};
            }

## Skip step because of remote checking
            next if !$param_error_step;

## Add failed template into HTML table
            $template_error .= "<a href='#$template_step'>$template_step</a>"
              . " <b>[$param_error_step]</b><br>";

## Add error in TXT
            $template_txt .=
              "\n>>> Template validation failed >> $template_step \n";

## Add nonmastered counter
            $nonmastered{ $step->{template} }++;
        }
        elsif ( $failed_atf->{ $step->{template} } ) {

## Add ATF with failed parameters into HTML table
            $template_review .= "$step->{template}<br>";

## Add mastered counter
            $mastered{ $step->{template} }++;
        }
        else {

## Add template into HTML table
            $template_ok .= "$step->{template}<br>";

## Add mastered counter
            $mastered{ $step->{template} }++;
        }
    }

## Define step's table
    my $template_table =
"<table><tr class=header><td class=error>Failed assignment by mastering</td>"
      . "<td class=review>Contains failed parameters</td><td>Passed</td></tr>";
    $template_table .= "<tr><td class=error>$template_error</td>";
    $template_table .= "<td class=review>$template_review</td>";
    $template_table .= "<td>$template_ok</td></tr></table>";

## Set parameters statistics
    my $param_summary = $self->message_param_summary($arf_param);

## Add Parameters summary in Text
    $template_txt .= "\n" . $param_summary . "\n";

## Define failed steps
    my %failed_step;

## Define variables for table with steps
    my $step_critical_error = "";
    my $step_major_error    = "";
    my $step_minor_error    = "";
    my $step_review         = "";
    my $step_ok             = "";

## Check step's failed parameters in ARF
    foreach my $step ( @{ $self->{_xml}->{step} } ) {

## Skip non-reviewed steps
        next if $step->{review} and $step->{review} eq 'false';

## Skip non-mastered template
        next if $nonmastered{ $step->{template} };

## Set param errors in step flag
        my $param_critical = 0;
        my $param_major    = 0;
        my $param_minor    = 0;

## Check all parameters
        foreach my $param ( @{ $step->{param} } ) {

## Skip parameter if it's from ATF
            next if !$param->{node};

## Skip referrer parameter
            next if $param->{severity} eq 'refer';

## Skip true parameter
            next if $param->{value};

## Check critical parameter
            if ( $param->{severity} eq 'critical' ) {

## Add critical error
                $param_critical++;

## Add message into ARF
                $self->message_critical() if !$self->{scenario};

## Goto next parameter
                next;
            }

## Check minor parameter
            if ( $param->{severity} eq 'minor' ) {

## Add minor error
                $param_minor++;

## Goto next parameter
                next;
            }

## Add major error
            $param_major++;
        }

## If errors found in step
        next if !$param_critical and !$param_major and !$param_minor;

## Format Step title
        my $template_step = $step->{template} . ":" . $step->{id};

## Add failed step for steps processing
        $failed_step{$template_step} =
          $param_critical + $param_major + $param_minor;

## Add error in TXT
        $template_txt .= "\n>>> Failed >> $template_step "
          . "[$param_critical critical][$param_major major][$param_minor minor] failed parameters\n";

## Set failed step
        my $step_error = "<a href='#$template_step'>$template_step</a>"
          . " <b class=critical>[$param_critical]</b><b class=major>[$param_major]</b><b class=minor>[$param_minor]</b><br>";

## If critical error exist
        if ($param_critical) {

## Add step as critical
            $step_critical_error .= $step_error;

## Goto next step
            next;
        }

## If minor error exist
        if ($param_minor) {

## Add step as minor
            $step_minor_error .= $step_error;

## Goto next step
            next;
        }

## Add step as major
        $step_major_error .= $step_error;
    }

## Show table with steps in ARF
    foreach my $step ( @{ $self->{_xml}->{step} } ) {

## Skip non-reviewed steps
        next if $step->{review} and $step->{review} eq 'false';

## Skip non-mastered template
        next if $nonmastered{ $step->{template} };

## Format Step title
        my $template_step = $step->{template} . ":" . $step->{id};

## If errors found in step
        if ( $failed_step{$template_step} ) {

## Add error in XHTML
        }

## If step_id has review, it should be manual reviewed
        elsif ( $step->{review} and $step->{review} eq 'true' ) {

## Add review into HTML table
            $step_review .= "<a href='#$template_step'>$template_step</a><br>";

## Add review in TXT
            $template_txt .= "\n>>> Manual review >> $template_step\n";
        }
        else {

## Add step in XHTML
            $step_ok .= "<a href='#$template_step'>$template_step</a><br>";
        }
    }

## Check errors in ARF
    foreach my $error ( @{ $self->{_xml}->{error} } ) {

## Add error in XHTML
        push(
            @{ $html->{body}->{p} },
            { class => "step_error", content => $error->{content} }
        );

## Add error in TXT
        $txt .= "\n>>> Error >> $error->{content}\n";
    }

## Add Line delimiter in XHTML
    push( @{ $html->{body}->{p} }, { hr => {} } );
    push( @{ $report->{body}->{p} }, { hr => {} } ) if $self->{report};

## Add ATF header in XHTML
    push(
        @{ $html->{body}->{p} },
        { class => "machine", content => "Template assignment report" }
    );

## Add step's table in XHTML
    push( @{ $html->{body}->{p} }, { content => $template_table } );

## Add Line delimiter in XHTML
    push( @{ $html->{body}->{p} }, { hr => {} } );

## Add ATF header in XHTML
    push(
        @{ $html->{body}->{p} },
        { class => "machine", content => "Audit results report" }
    );

## Add Parameters summary in XHTML
    push(
        @{ $html->{body}->{p} },
        { pre => { class => "output", content => $param_summary } }
    );

## If critical errors exist
    $step_critical_error =
      "<br>&nbsp;<b class=critical><u>Critical</u></b>:<br><br>"
      . $step_critical_error . "<br>"
      if $step_critical_error;

## If major errors exist
    $step_major_error =
      "<br>&nbsp;<b class=major><u>Major</u></b>:<br><br>"
      . $step_major_error . "<br>"
      if $step_major_error;

## If minor errors exist
    $step_minor_error =
      "<br>&nbsp;<b class=minor><u>Minor</u></b>:<br><br>"
      . $step_minor_error . "<br>"
      if $step_minor_error;

## Define step's table
    my $step_table =
"<table><tr class=header><td class=error>Failed [<b class=failed>critical</b>][<b class=major>major</b>][<b class=minor>minor</b>]</td>"
      . "<td class=review>Manual review</td><td>Passed</td></tr>";
    $step_table .=
"<tr><td class=error>$step_critical_error$step_major_error$step_minor_error</td>";
    $step_table .= "<td class=review>$step_review</td>";
    $step_table .= "<td>$step_ok</td></tr></table>";

## Add step's table in XHTML
    push( @{ $html->{body}->{p} }, { content => $step_table } );

## Add templates and steps into txt
    $txt .= $template_txt;

## Remove all XML tags from TXT
    $txt =~ s/<[a-zA-Z\/][^>]*>//g;

## Add ATF header in XHTML
    push(
        @{ $report->{body}->{p} },
        { class => "machine", content => "Summary report" }
    ) if $self->{report};

## Define summary table
    my $summary_table =
      "<table><tr class=machine><td rowspan=2><b>Parameters</b></td>";
    $summary_table .=
      "<td colspan=99><b>Machines</b></td></tr><tr class=header>";

## Define titles for node
    my %node_titles;

## Check all machines in ARF
    foreach my $machine ( @{ $self->{_xml}->{machine} } ) {

## Check all node in ARF
        foreach my $node ( @{ $machine->{node} } ) {

## Check titles in ARF
            foreach my $title ( @{ $self->{_xml}->{title} } ) {

## Get title's step
                my $step =
                  $self->get_step( $title->{step}, $title->{template} );

## Skip wrong step
                next if !$step;

## Skip non-reviewed steps
                next if $step->{review} and $step->{review} eq 'false';

## Parameters parsing in Step
                foreach my $static ( @{ $step->{static} } ) {

## Skip wrong parameter
                    next
                      if (  $static->{node} ne $node->{host}
                        and $static->{rnode} ne $node->{host} )
                      or $static->{id} ne $title->{id};

## Define static's content
                    $node_titles{ $node->{host} }++ if $static->{content} ne "";
                }
            }

## Skip non-interested node
            next if !$node_titles{ $node->{host} };

## Add node into summary table header
            $summary_table .= "<td class=machine_d>$node->{host}</td>";
        }
    }

## Close header in table
    $summary_table .= "</tr>";

## Define current summary template
    my $current_step = "";

## Check titles in ARF
    foreach my $title ( @{ $self->{_xml}->{title} } ) {

## Get title's step
        my $step = $self->get_step( $title->{step}, $title->{template} );

## Skip wrong step
        next if !$step or $title->{id} eq $title->{step};

## Skip non-reviewed steps
        next if $step->{review} and $step->{review} eq 'false';

## Add step if titles from new step next
        if ( $step->{id} ne $current_step ) {

## Get title
            my $step_title =
              $self->get_title( $step->{template}, $step->{id}, $step->{id} );

## Set current summary template
            $current_step = $step->{id};

## Define summary template
            my $summary_step = $current_step;

## Set summary template
            $summary_step .= " - " . $step_title->{content}
              if $step_title and $step_title->{content};

## Add summary template
            $summary_table .= "<tr class=header><td colspan=3 class=machine>";
            $summary_table .= "$summary_step</td></tr>";
        }

## Add parameter title
        $summary_table .=
          "<tr class=report><td nowrap class=machine_l>$title->{content}</td>";

## Check all machines in ARF
        foreach my $machine ( @{ $self->{_xml}->{machine} } ) {

## Check all node in ARF
            foreach my $node ( @{ $machine->{node} } ) {

## Skip non-interested node
                next if !$node_titles{ $node->{host} };

## Define static's content
                my $content = "";

## Parameters parsing in Step
                foreach my $static ( @{ $step->{static} } ) {

## Show remote statis for host
                    if (    $static->{rnode} eq $node->{host}
                        and $static->{id} eq $title->{id} )
                    {

## Define static's content
                        $content .= $static->{content};

## Add remote node to node title
                        $content .= "&nbsp;[" . $static->{node} . "]"
                          if $static->{node};

## Add break line
                        $content .= "<br>";

## Goto next
                        next;
                    }

## Skip wrong parameter
                    next
                      if $static->{node} ne $node->{host}
                          or $static->{id} ne $title->{id};

## Get static's parameter in step
                    my $static_param =
                      $self->check_node_param( $step, $static->{id},
                        $static->{node}, $static->{rnode} );

## Check parameter's value
                    if ( $static_param and $static_param->{value} != 1 ) {

## Show checked parameter
                        if (    $static_param->{check}
                            and $static_param->{check} ne "" )
                        {
                            $content .=
"<b class=failed>$static->{content} (Expected $static_param->{check})</b>";
                        }
                        else {

## Define static's content as Reviewed
                            $content .=
                              "<b class=review>$static->{content}</b>";
                        }
                    }
                    else {

## Define static's content
                        $content .= $static->{content};
                    }

## Add remote node to node title
                    $content .= "&nbsp;[" . $static->{rnode} . "]"
                      if $static->{rnode};

## Add break line
                    $content .= "<br>";
                }

## If content is empty, change to -
                $content = "-" if $content eq '';

## Add parameter head and value
                $summary_table .= "<td nowrap class=machine>$content</td>";
            }
        }

## Add parameter into summary
        $summary_table .= "</tr>";
    }

## Set end of summary table
    $summary_table .= "</table>";

## Add summary's table in XHTML
    push( @{ $report->{body}->{p} }, { content => $summary_table } )
      if $self->{report};

## Add Line delimiter in XHTML
    push( @{ $html->{body}->{p} }, { hr => {} } );

## Show steps in ARF
    foreach my $step ( @{ $self->{_xml}->{step} } ) {

## Skip non-reviewed steps
        next if $step->{review} and $step->{review} eq 'false';

## Format Step title
        my $template_step = $step->{template} . ":" . $step->{id};

## Add Step's name for link in XHTML
        push( @{ $html->{body}->{p} }, { a => { name => $template_step } } );

## Add step's title in XHTML
        push(
            @{ $html->{body}->{p} },
            { class => "step", content => $template_step }
        );

## Set step's title
        my $step_title = "*";
        $step_title .= "*"
          while ( length($step_title) < length($template_step) + 4 );
        $step_title = "\n\n$step_title\n* $template_step *\n$step_title\n";

## Add step's title in TXT
        $txt .= $step_title;

## Format Hint for step
        my $hint      = "Hint: ";
        my $step_hint = $self->get_step_hint($step);

## Set Hint
        if ( !$step_hint or !$step_hint->{content} ) {
            $hint .= $self->message_no_hint();
        }
        else {
            $hint .= $step_hint->{content};
        }

## Add step's hint in XHTML
        push(
            @{ $html->{body}->{p} },
            { pre => { class => "hint", content => $hint } }
        );

## Add step's hint in TXT
        $txt .= "\n$hint\n";

## Static processing in Step
        foreach my $static ( @{ $step->{static} } ) {

## Skip static for parameters
            next if $static->{id};

## Format node title
            my $node_ref = "=> " . $static->{node};

## Add remote node to node title
            $node_ref .= "&" . $static->{rnode} if $static->{rnode};

## If referer specified, add it to node
            $node_ref .= " [" . $static->{ref} . "]" if $static->{ref};

## If step N/A for node
            if ( $static->{content} =~ /^N\/A/ ) {

## Add static node with ref in XHTML
                push(
                    @{ $html->{body}->{p} },
                    { content => $node_ref . ": N/A" }
                ) if !$self->{Process}->is_exclude();

## Goto next static
                next;
            }

## Get command from ARF
            my $step_cmd =
              $self->get_step_command( $step, $static->{node}, $static->{rnode},
                1 );

## Add command to node title
            $node_ref .= ": " . $step_cmd->{content};

## Add time to step
            $node_ref .= " [execution time: $static->{time} sec.]"
              if $static->{time};

## Add static node with ref in XHTML
            push( @{ $html->{body}->{p} }, { content => $node_ref } );

## Add static node with ref in TXT
            $txt .= "\n$node_ref \n";

## Define content's variables as empty by default
            my $content = "No content";

## If static's content exists
            $content = $static->{content} if $static->{content} ne '';

## Make content copy for hightlighting
            my $content_pre = $content;

## Parameters parsing in Step
            foreach my $param ( @{ $step->{param} } ) {

## Skip wrong parameter
                next
                  if !$param->{node}
                      or $static->{node}  ne $param->{node}
                      or $static->{rnode} ne $param->{rnode};

## Set param's class
                my $param_class = "passed";

## Set failed param's class if value failed
                $param_class = "failed" if !$param->{value};

## Find parameter's content
                $content_pre =~ /$param->{content}/;

## Set parameter's content
                my $param_content = $&;

## Get static for parameter
                my $param_static =
                  $self->get_step_static( $step, $param->{node},
                    $param->{rnode}, $param->{id} );

## If static found
                if ( $param_static and $param_static->{content} ne '' ) {

## Hightlight value
                    eval {
                        $param_content =~
s/\Q$param_static->{content}\E/<b class=review>$&<\/b>/;
                    };
                }

## Hightlight parameters
                eval {
                    $content_pre =~
s/$param->{content}/<b class=$param_class>$param_content<\/b>/;

                };

## Skip true and referrer parameter
                next if $param->{value} or $param->{severity} eq 'refer';

## Define rule representation
                my $param_hint = $self->get_step_hint( $step, $param->{id} );

## Get no hint message
                my $failed_hint = $self->message_no_hint();

## If Parameter's hint specified use it
                if ( $param_hint and $param_hint->{content} ) {
                    $failed_hint = $param_hint->{content};
                }

## Check compare parameters
                my $compare_hint = $self->get_step_hint( $step,
                        "cmp_"
                      . $param->{id} . "_"
                      . $param->{node} . "_"
                      . $param->{rnode} );

## If Compare hint specified use it
                if ( $compare_hint and $compare_hint->{content} ) {
                    $failed_hint .= $compare_hint->{content};
                }

## Format failed parameter
                my $failed =
"<b><u>$param->{id} [$param->{severity}]:</u></b> $failed_hint";

## Add param in XHTML
                push(
                    @{ $html->{body}->{p} },
                    {
                        pre =>
                          { class => $param->{severity}, content => $failed }
                    }
                );

## Add param in TXT
                $txt .= "\n>>>>>> $failed\n";
            }

## Define variable for count lines in content
            my $content_lines = 0;

## Count lines in content
            $content_lines++ while $content =~ /\n/g;

## Add static in XHTML
            if (    $content_lines >= 16
                and !$self->{Process}->is_debug()
                and !$self->{scenario} )
            {
                push(
                    @{ $html->{body}->{p} },
                    { textarea => { content => $content, wrap => "off" } }
                );
            }
            else {
                push(
                    @{ $html->{body}->{p} },
                    { pre => { class => "static", content => $content_pre } }
                );
            }

## Add static in TXT
            $txt .= "\n$content\n";
        }

## Add TOP in Step
        push(
            @{ $html->{body}->{p} },
            {
                content =>
                  "<A href='javascript:history.back(1)'>Back</A>&nbsp;&nbsp;|"
                  . "&nbsp;&nbsp;<a href='#top'>Top</a>"
            }
        );

## Add Line delimiter
        push( @{ $html->{body}->{p} }, { hr => {} } );
    }

## Return HTML if requested
    if ( $request eq 'html' ) {

## Return HTML
        return eval {
            $self->{Process}->{ACF}->{xs}->XMLout(
                $html,
                RootName => 'html',
                NoEscape => 1
            );
        };
    }

## Return HTML if requested
    if ( $request eq 'report' ) {

## Return HTML
        return eval {
            $self->{Process}->{ACF}->{xs}->XMLout(
                $report,
                RootName => 'html',
                NoEscape => 1
            );
        };
    }

## Return Text if requested
    return $txt if $request eq 'txt';

## Generate TXT description
    if ( open( LOGFILE, '>' . $self->{directory} . "/$self->{id}.txt" ) ) {
        print LOGFILE $txt;
        close(LOGFILE);
    }

## Generate HTML description
    eval {
        $self->{Process}->{ACF}->{xs}->XMLout(
            $html,
            RootName   => 'html',
            OutputFile => $self->{directory} . "/" . $self->{id} . ".html",
            NoEscape   => 1
        );
    };

## Generate HTML description
    eval {
        $self->{Process}->{ACF}->{xs}->XMLout(
            $report,
            RootName   => 'html',
            OutputFile => $self->{directory} . "/" . $self->{report} . ".html",
            NoEscape   => 1
        );
    } if $self->{report};

## Return Ok
    return 1;

} ## end sub generate

##############################################################################
#
# get_argument()
#
# Return argument for argument identifier
#
# Return:
# - Argument hash  - if it exists in ARF
# - 0              - otherwise
#
# Arguments expected are:
# - self           - Object reference
# - template_id   - Template's Identifier
# - argument_id    - Argument's id

sub get_argument {
    my ( $self, $template_id, $argument_id ) = @_;

## Check arguments in ATF
    foreach my $argument ( @{ $self->{_xml}->{argument} } ) {

## Skip wrong template
        next if $template_id and $argument->{template} ne $template_id;

## Skip wrong argument
        next if $argument->{id} ne $argument_id;

## Return Argument hash
        return $argument;
    }

## If argument doesn't exist in ARF, return 0
    return 0;

} ## end sub get_argument

##############################################################################
#
# get_audit_end()
#
# Return ARF end-time-date
#
# Return:
# - time and date
#
# Arguments expected are:
# - self    - Object reference

sub get_audit_end {
    my ($self) = @_;

## Return End-Date
    return $self->{_xml}->{end};

} ## end sub get_audit_end

##############################################################################
#
# get_audit_start()
#
# Return ARF start-time-date
#
# Return:
# - time and date
#
# Arguments expected are:
# - self    - Object reference

sub get_audit_start {
    my ($self) = @_;

## Return Start-Date
    return $self->{_xml}->{start};

} ## end sub get_audit_start

##############################################################################
#
# get_id()
#
# Return ARF Identifier
#
# Return:
# - identifier
#
# Arguments expected are:
# - self    - Object reference

sub get_id {
    my ($self) = @_;

## Return Identifier
    return $self->{_xml}->{id};

} ## end sub get_id

##############################################################################
#
# get_list_machine()
#
# Return:
# - list of machines
# - 0             - if list empty
#
# Arguments expected are:
# - self          - Object reference

sub get_list_machine {
    my ($self) = @_;

## Return Machine list
    return $self->{_xml}->{machine} if $self->{_xml}->{machine};

## Return Error
    return 0;

} ## end sub get_list_machine

##############################################################################
#
# get_list_session()
#
# Return:
# - list of sessions
# - 0             - if list empty
#
# Arguments expected are:
# - self          - Object reference

sub get_list_session {
    my ($self) = @_;

## Return Error, if no sessions opened
    return 0 if !$self->{_sessions} or !@{ $self->{_sessions} };

## Return Machine list
    return $self->{_sessions};

} ## end sub get_list_session

##############################################################################
#
# get_machine()
#
# Return ASF machine for specified node's Identifier
#
# Return:
# - Machine hash    - it exists in ARF
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object Reference
# - node_id         - Node's Identifier

sub get_machine {
    my ( $self, $node_id ) = @_;

## Return Error, if no machines found
    return 0 if !$self->get_list_machine();

## Check machines in ASF
    foreach my $machine ( @{ $self->get_list_machine() } ) {

## Return Machine hash
        my $node = $self->get_machine_node( $machine, $node_id );

## Return Machine hash if node found
        return $machine if $node;
    }

## If Machine doesn't exist in ASF, return Error
    return 0;

} ## end sub get_machine

##############################################################################
#
# get_machine_id()
#
# Return ARF Machine's Identifier
#
# Return:
# - identifier
#
# Arguments expected are:
# - self     - Object reference
# - node_id  - Node's Identifier

sub get_machine_id {
    my ( $self, $node_id ) = @_;

## Get Machine
    my $machine = $self->get_machine($node_id);

## Return Error, if no machine found
    return 0 if !$machine;

## Return Identifier
    return $machine->{id};

} ## end sub get_machine_id

##############################################################################
#
# get_machine_passwd()
#
# Return Machine's password for login
#
# Return:
# - Password hash  - if it exists
# - 0              - otherwise
#
# Arguments expected are:
# - self           - Object reference
# - node_id        - Node's Identifier
# - login          - Password's Login

sub get_machine_passwd {
    my ( $self, $node_id, $login ) = @_;

## Get Machine
    my $machine = $self->get_machine($node_id);

## Return Error, if no machine found
    return 0 if !$machine;

## Check password in Machine
    foreach my $passwd ( @{ $machine->{passwd} } ) {

## Skip wrong passwd
        next if $login ne $passwd->{login};

## Return Passwd hash
        return $passwd;
    }

## If passwd not specified in Machine, return 0
    return 0;

} ## end sub get_machine_passwd

##############################################################################
#
# get_machine_root()
#
# Get Machine's root using defaults from ACF
#
# Return:
# - Root hash     - if Root hash filled sucessfully
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference
# - node_id        - Node's Identifier

sub get_machine_root {
    my ( $self, $node_id ) = @_;

## Get Machine
    my $machine = $self->get_machine($node_id);

## Return Error, if no machine found or root not specified
    return 0 if !$machine or !$machine->{root} or !$machine->{root}->[0];

## Get root hash
    my $mach_root = $machine->{root}->[0];

## If root is invalid, return Error
    return 0 if !$mach_root->{content} or !$mach_root->{login};

## Return root hash
    return $mach_root;

} ## end sub get_machine_root

##############################################################################
#
# get_machine_template()
#
# Return Machine's template for template's Identifier
#
# Return:
# - Template hash   - if it exists
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object reference
# - node_id         - Node's Identifier
# - template_id     - Template Id

sub get_machine_template {
    my ( $self, $node_id, $template_id ) = @_;

## Get Machine
    my $machine = $self->get_machine($node_id);

## Return Error, if no machine found
    return 0 if !$machine;

## Check templates in Machine
    foreach my $template ( @{ $machine->{template} } ) {

## Skip wrong template
        next if $template_id ne $template->{id};

## Return Template Hash
        return $template;
    }

## If template doesn't exist, return Error
    return 0;

} ## end sub get_machine_template

##############################################################################
#
# get_machine_unit()
#
# Return ARF Machine's Unit
#
# Return:
# - unit
#
# Arguments expected are:
# - self       - Object reference
# - node_id    - Node's Identifier

sub get_machine_unit {
    my ( $self, $node_id ) = @_;

## Get Machine
    my $machine = $self->get_machine($node_id);

## Return Error, if no machine found
    return 0 if !$machine;

## Return Unit
    return $machine->{unit};

} ## end sub get_machine_unit

##############################################################################
#
# get_machine_node()
#
# Return Machine's node for node's Identifier
#
# Return:
# - Node hash   - if it exists
# - 0           - otherwise
#
# Arguments expected are:
# - self        - Object reference
# - machine     - Machine hash
# - node_id     - Node's Identifier

sub get_machine_node {
    my ( $self, $machine, $node_id ) = @_;

## Check node in Machine
    foreach my $node ( @{ $machine->{node} } ) {

## Skip wrong node
        next if $node_id ne $node->{host};

## Return Node hash
        return $node;
    }

## If node doesn't exist, return 0
    return 0;

} ## end sub get_machine_node

##############################################################################
#
# get_node()
#
# Return Machine's node for node's Identifier
#
# Return:
# - Node hash   - if it exists
# - 0           - otherwise
#
# Arguments expected are:
# - self        - Object reference
# - node_id     - Node's Id

sub get_node {
    my ( $self, $node_id ) = @_;

## Return Error, if no machines found
    return 0 if !$node_id or !$self->get_list_machine();

## Check machines in ASF
    foreach my $machine ( @{ $self->get_list_machine() } ) {

## Return Machine hash
        my $node = $self->get_machine_node( $machine, $node_id );

## Return Node hash if node found
        return $node if $node;
    }

## If node doesn't exist, return error
    return 0;

} ## end sub get_node

##############################################################################
#
# get_node_id()
#
# Return Initial Node's identifier
#
# Return:
# - node's identifier
#
# Arguments expected are:
# - self    - Object reference

sub get_node_id {
    my ($self) = @_;

## Return Version
    return $self->{_node}->{host};

} ## end sub get_node_id

##############################################################################
#
# get_node_prompt()
#
# Return Node's prompt for login
#
# Return:
# - Prompt hash  - if it exists
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - node         - Node hash
# - login        - Login

sub get_node_prompt {
    my ( $self, $node, $login ) = @_;

## Check prompts in node
    foreach my $prompt ( @{ $node->{prompt} } ) {

## Skip wrong prompt
        next if $login ne $prompt->{login};

## Return Prompt hash
        return $prompt;
    }

## If prompt doesn't exist, return error
    return 0;

} ## end sub get_node_prompt

##############################################################################
#
# get_session()
#
# Return session
#
# Return:
# - Session Object  - if it's opened
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object reference
# - session_id      - Session's id

sub get_session {
    my ( $self, $session_id ) = @_;

## Return Error, if no sessions opened
    return 0 if !$self->{_sessions};

## Check opened session
    foreach my $session ( @{ $self->{_sessions} } ) {

## Skip wrong session
        next if !$session or $session->{id} ne $session_id;

## Return Session Object
        return $session;
    }

## If session not found, return Error
    return 0;

} ## end sub get_session

##############################################################################
#
# get_site()
#
# Return ARF's site
#
# Return:
# - site
#
# Arguments expected are:
# - self    - Object reference

sub get_site {
    my ($self) = @_;

## Return Version
    return $self->{_xml}->{site};

} ## end sub get_site

##############################################################################
#
# get_statistics()
#
# Return ARF statistics
#
# Return:
# - (parameters, failed, passed) - parsed sucessfully
# - 0                            - error happends
#
# Arguments expected are:
# - self                         - Object reference

sub get_statistics {
    my ($self) = @_;

## Define variable for templates and parameters
    my ( %param, %failed, %passed );

## Define counters
    $param{ok}    = 0;
    $param{error} = 0;
    $param{na}    = 0;

## Check steps in ARF
    foreach my $step ( @{ $self->{_xml}->{step} } ) {

## Skip non-mastered steps
        next if $step->{review} and $step->{review} eq 'false';

## Check parameters in Step
        foreach my $param ( @{ $step->{param} } ) {

## Skip parameter if node not specified, it's from ATF
            next if !$param->{node};

## Skip referrer parameter
            next if $param->{severity} eq 'refer';

## If parameter is Ok
            if ( $param->{value} == 1 ) {
                $param{ok}++;

## Count passed parameters in template
                $passed{ $step->{template} }++;

## Goto next parameter
                next;
            }

## If parameter is Error
            if ( $param->{value} == 0 ) {
                $param{error}++;

## Count failed parameters in template
                $failed{ $step->{template} }++;

## Goto next parameter
                next;
            }

## If parameter is N/A
            if ( $param->{value} == 2 ) {
                $param{na}++;

## Goto next parameter
                next;
            }
        }
    }

## Return parameters statistics, failed and passed templates
    return ( \%param, \%failed, \%passed );

} ## end sub get_statistics

##############################################################################
#
# get_step()
#
# Return step for step id and template id
#
# Return:
# - Step hash     - if it exists in ARF
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference
# - step_id       - Step's id
# - template_id   - Template's id

sub get_step {
    my ( $self, $step_id, $template_id ) = @_;

## Check step in ARF
    foreach my $step ( @{ $self->{_xml}->{step} } ) {

## Skip step if template wrong
        next if $template_id and $step->{template} ne $template_id;

## Skip wrong step
        next if $step->{id} ne $step_id;

## Return Step hash
        return $step;
    }

## If step doesn't exist in ARF, return Error
    return 0;

} ## end sub get_step

##############################################################################
#
# get_step_argument()
#
# Return Step's argument
#
# Return:
# - Argument Hash   - if it exists in Step
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object reference
# - step            - Step hash
# - argument_id     - Argument's id
# - node_id         - Node's Identifier
# - remote_id       - Remote Node's Identifier

sub get_step_argument {
    my ( $self, $step, $argument_id, $node_id, $remote_id ) = @_;

## Define copy of argument
    my $argument_copy;

## Check arguments in step
    foreach my $step_argument ( @{ $step->{argument} } ) {

## Skip wrong argument
        next if $step_argument->{node} or $step_argument->{id} ne $argument_id;

## Create copy of argument
        $argument_copy = {
            id      => $step_argument->{id},
            content => $step_argument->{content}
        };

## Set additional attributes of argument
        $argument_copy->{login} = $step_argument->{login}
          if $step_argument->{login};
        $argument_copy->{step} = $step_argument->{step}
          if $step_argument->{step};
        $argument_copy->{template} = $step_argument->{template}
          if $step_argument->{template};
        $argument_copy->{type} = $step_argument->{type}
          if $step_argument->{type};
        $argument_copy->{unit} = $step_argument->{unit}
          if $step_argument->{unit};
        $argument_copy->{remote} = $step_argument->{remote}
          if $step_argument->{remote};
    }

## If argument doesn't exist in step, return error
    return 0 if !$argument_copy;

## If argument use step's output
    if (    $argument_copy->{step}
        and $argument_copy->{content}
        and $node_id
        and $self->{_sessions} )
    {

## Set referrer nodes as local
        my $ref_node_id   = $node_id;
        my $ref_remote_id = $remote_id;

## Change referrer nodes as remote
        if (    $remote_id
            and $argument_copy->{remote}
            and $argument_copy->{remote} eq 'true' )
        {
            $ref_node_id   = $remote_id;
            $ref_remote_id = $node_id;
        }

## Check referrer - argument step and parameter
        my ( $argument_step, $argument_param ) =
          $self->execute_refer( $argument_copy, $ref_node_id, $ref_remote_id );

## If step exist in ARF
        if (    $argument_step
            and $argument_step->{id}
            and $argument_param
            and $argument_param->{id}
            and $argument_param->{value}
            and $argument_param->{value} != 2 )
        {

## Check parameter's content
            my $param_static = $self->get_step_static(
                $argument_step, $ref_node_id,
                $ref_remote_id, $argument_param->{id}
            );

## If Parameter's static defined
            if ($param_static) {
                $argument_copy->{content} = $param_static->{content};
            }
            else {

## Add parameter invalid content message into ARF
                $self->message_invalid_param( $ref_node_id, $ref_remote_id,
                    $argument_param->{id}, $argument_step->{id} );

## Set empty content
                $argument_copy->{content} = "";
            }
        }
        else {

## Set empty content
            $argument_copy->{content} = "";
        }

## If referrer specified, but not referrer's step found
        if ( !$argument_step ) {

## Add error into ARF
            $self->message_invalid_refer( $ref_node_id, $ref_remote_id,
                $argument_copy->{step} );
        }
    }

## Get argument from ARF
    my $argument =
      $self->get_argument( $argument_copy->{template}, $argument_copy->{id} );

## If global argument specified
    if ( $argument and $argument->{content} and !$argument_copy->{content} ) {

## Set argument's content as global
        $argument_copy->{content} = $argument->{content};
    }

## Return Argument
    return $argument_copy;

} ## end sub get_step_argument

##############################################################################
#
# get_step_command()
#
# Return Step's command
#
# Return:
# - Command hash  - if it exists in Step
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference
# - step          - Step hash
# - node_id       - Node's Identifier
# - remote_id     - Remote Node's Identifier
# - nopasswd      - If 1, then all password argument will be changed to ********

sub get_step_command {
    my ( $self, $step, $node_id, $remote_id, $nopasswd ) = @_;

## If command not specified, return 0
    return 0 if !$step->{command} or !$step->{command}->[0];

## Get first command
    my $command = $step->{command}->[0];

## Create copy of command for modifying
    my $command_copy = {
        login   => $command->{login},
        content => $command->{content}
    };

## Set additional attributes of command
    $command_copy->{timeout} = 0;
    $command_copy->{timeout} = $command->{timeout} if $command->{timeout};
    $command_copy->{end}     = $command->{end} if $command->{end};
    $command_copy->{remote}  = $command->{remote} if $command->{remote};

## Define command's node as local
    my $cmd_node_id = $node_id;

## Set command's node as remote
    $cmd_node_id = $remote_id
      if $remote_id
          and $command_copy->{remote}
          and $command_copy->{remote} =~ /(true|static)/;

## If command's password specified
    if ( $command->{passwd} and $remote_id ) {

## Get argument from ARF
        my $argument =
          $self->get_argument( $step->{template}, $command->{passwd} );

## Check if argument type is passwd and nopasswd not specified
        if (    $argument
            and $argument->{type} eq 'passwd'
            and $argument->{login} )
        {

## Get password from ARF
            my $passwd =
              $self->decrypt_passwd( $remote_id, $argument->{login} );

## If password specified and non-empty
            if ( $passwd and $passwd->{content} ) {

## Redefine command's password
                $command_copy->{passwd} = $passwd->{content};
            }
            else {

## Add message into ARF
                $self->message_invalid_passwd( $argument->{login} );
            }
        }
        else {

## Add argument invalid content message into ARF
            $self->message_invalid_argument( $remote_id, $argument->{id},
                $step->{id} );
        }
    }

## If Step's arguments not specified
    if ( !$step->{argument} ) {

## Return Command hash
        return $command_copy;
    }

## Check arguments in step
    foreach my $step_argument ( @{ $step->{argument} } ) {

## Skip parameters with specified node
        next if $step_argument->{node};

## Skip argument if it's not exist in command
        next if $command_copy->{content} !~ $step_argument->{id};

## Define argument's content
        my $argument_content = "NULL";

## Get argument for node
        my $node_argument =
          $self->check_node_argument( $step, $step_argument->{id}, $node_id,
            $remote_id );

## Next if no node argument
        next if !$node_argument;

## If node's argument defined
        if ( $node_argument and $node_argument->{content} ) {

## Redefine argument
            $argument_content = $node_argument->{content};
        }

## Get argument from ARF
        my $argument = $self->get_argument( $step_argument->{template},
            $step_argument->{id} );

## Check if argument type is passwd and nopasswd not specified
        if (    $argument
            and $argument->{type}
            and $argument->{type} eq 'passwd'
            and $argument->{login}
            and !$nopasswd )
        {

## Get password from ARF
            my $passwd =
              $self->decrypt_passwd( $cmd_node_id, $argument->{login} );

## If password specified and non-empty
            if ( $passwd and $passwd->{content} ) {

## Redefine argument's content
                $argument_content = $passwd->{content};
            }
            else {

## Add message into ARF
                $self->message_invalid_passwd( $argument->{login} );

## Return Error
                return 0;
            }

## Changed node's argument's content to ********
            $node_argument->{content} = "********";
        }

## Replace argument with content
        $command_copy->{content} =~ s/$step_argument->{id}/$argument_content/g;
    }

## Return command hash
    return $command_copy;

} ## end sub get_step_command

##############################################################################
#
# get_step_hint()
#
# Return Step's hint for Parameter's Identifier or command
#
# Return:
# - Hint hash    - if it exists in Step
# - 0            - otherwise
#
# Arguments expected are:
# - self         - Object reference
# - step         - Step hash
# - hint_id      - Hint's id

sub get_step_hint {
    my ( $self, $step, $hint_id ) = @_;

## Check hint in step
    foreach my $hint ( @{ $step->{hint} } ) {

## Return Hint for command
        return $hint if !$hint_id and !$hint->{id};

## Skip wrong hint
        next if !$hint->{id} or $hint->{id} ne $hint_id;

## Return Hint hash
        return $hint;
    }

## If hint doesn't exist in step, return error
    return 0;

} ## end sub get_step_hint

##############################################################################
#
# get_step_param()
#
# Return Step's parameters for Parameter's Identifier
#
# Return:
# - Parameter hash  - if it exists in Step
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object reference
# - step            - Step hash
# - param_id        - Parameter's id
# - node_id         - Node's Identifier
# - remote_id       - Remote Node's Identifier

sub get_step_param {
    my ( $self, $step, $param_id, $node_id, $remote_id ) = @_;

## Define copy of parameter
    my $param_copy;

## Check parameters in step
    foreach my $param ( @{ $step->{param} } ) {

## Skip parameters with specified node or wrong parameter
        next if $param->{node} or $param->{id} ne $param_id;

## Create copy of parameter
        $param_copy = {
            id      => $param->{id},
            method  => $param->{method},
            content => $param->{content}
        };

## Set additional attributes of parameter
        $param_copy->{compare} = $param->{compare} if $param->{compare};
        $param_copy->{severity} = $param->{severity}
          if $param->{severity};
        $param_copy->{check}    = $param->{check}    if $param->{check};
        $param_copy->{template} = $param->{template} if $param->{template};
    }

## If parameter doesn't exist in step, return error
    return 0 if !$param_copy;

## If Step's arguments not specified or no content
    if ( !$step->{argument} or !$param_copy->{content} ) {

## Return Param hash
        return $param_copy;
    }

## Check arguments in step
    foreach my $step_argument ( @{ $step->{argument} } ) {

## Skip parameters with specified node
        next if $step_argument->{node};

## Skip argument if it's not exist in command
        next if $param_copy->{content} !~ $step_argument->{id};

## Define argument's content
        my $argument_content = "NULL";

## Get argument for node
        my $node_argument =
          $self->check_node_argument( $step, $step_argument->{id}, $node_id,
            $remote_id );

## If node's argument defined
        if ( $node_argument and $node_argument->{content} ) {

## Redefine argument
            $argument_content = $node_argument->{content};
        }

## Change argument's Identifier to value if content specified
        $param_copy->{content} =~ s/$step_argument->{id}/$argument_content/g;
    }

## Return Param Hash
    return $param_copy;

} ## end sub get_step_param

##############################################################################
#
# get_step_static()
#
# Return Step's static for Node's Identifier
#
# Return:
# - Static hash    - if exists in Step
# - 0              - otherwise
#
# Arguments expected are:
# - self           - Object reference
# - step           - Step hash
# - node_id        - Node's id
# - remote_id      - Remote Node's id
# - static_id      - Static's id

sub get_step_static {
    my ( $self, $step, $node_id, $remote_id, $static_id ) = @_;

## Define counter of arguments with step
    my $argument_step = 0;

## Get first command in step
    my $command = $step->{command}->[0];

## If remote node specified
    if ($remote_id) {

## Increment counter if remote command specified
        $argument_step++ if $command->{remote} and $command->{remote} eq 'true';

## Check argument in step
        foreach my $argument ( @{ $step->{argument} } ) {

## Skip argument if it's not exist in command
            next if $command->{content} !~ $argument->{id};

## Increment counter of arguments with step
            $argument_step++;
        }
    }

## If remote node specified
    if ( $remote_id and $command->{remote} and $command->{remote} eq 'static' )
    {

## Check static in step
        foreach my $static ( @{ $step->{static} } ) {

## Skip wrong static
            next if $static->{rnode} ne $remote_id;

## Return Static for command
            return $static if !$static_id and !$static->{id};

## Skip wrong static
            next if !$static->{id} or $static->{id} ne $static_id;

## Return Static hash
            return $static;
        }

## If static doesn't exist in step, return error
        return 0;
    }

## Check static in step
    foreach my $static ( @{ $step->{static} } ) {

## Skip wrong static
        next if $static->{node} ne $node_id;

## Skip wrong static
        next
          if $remote_id
              and $argument_step
              and $static->{rnode} ne $remote_id;

## Return Static for command
        return $static if !$static_id and !$static->{id};

## Skip wrong static
        next if !$static->{id} or $static->{id} ne $static_id;

## Return Static hash
        return $static;
    }

## If static doesn't exist in step, return error
    return 0;

} ## end sub get_step_static

##############################################################################
#
# get_title()
#
# Return title
#
# Return:
# - Title hash    - it exists in ARF
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference
# - template_id   - Template's Identifier
# - step_id       - Step's Identifier
# - param_id      - Parameter's Identifier

sub get_title {
    my ( $self, $template_id, $step_id, $param_id ) = @_;

## Check title in ARF
    foreach my $title ( @{ $self->{_xml}->{title} } ) {

## Skip wrong template
        next if $title->{template} ne $template_id;

## Return Title if no step or param specified
        return $title if !$param_id and !$title->{id};

## Skip wrong title
        next if !$title->{id} or !$title->{step};
        next if $title->{step} ne $step_id or $title->{id} ne $param_id;

## Return Title hash
        return $title;
    }

## If title not specified, return Error
    return 0;

} ## end sub get_title

##############################################################################
#
# get_version()
#
# Return ARF Version
#
# Return:
# - version
#
# Arguments expected are:
# - self    - Object reference

sub get_version {
    my ($self) = @_;

## Return Version
    return $self->{_xml}->{version};

} ## end sub get_version

##############################################################################
#
# mastering_machine()
#
# Mastering Machine using defaults from ACF
#
# Return:
# - 1         - if Node updated
# - 0         - otherwise
#
# Arguments expected are:
# - self      - Object reference
# - node_id   - Node's Identifier

sub mastering_machine {
    my ( $self, $node_id ) = @_;

## Get Machine
    my $machine = $self->get_machine($node_id);

## Update password in node
    foreach my $node ( @{ $machine->{node} } ) {

## Check passwords specified in Machine
        foreach my $passwd ( @{ $machine->{passwd} } ) {

## Get prompt from node
            my $node_prompt = $self->get_node_prompt( $node, $passwd->{login} );

## Goto next prompt
            next if $node_prompt and $node_prompt->{content};

## Get prompt from ACF
            my $default_prompt =
              $self->{Process}->{ACF}->get_prompt( $passwd->{login} );

## Skip, if it's not found in ACF
            next if !$default_prompt;

## Add prompt
            push( @{ $node->{prompt} }, $default_prompt );

## Set machine updated state
            $self->{machine_updated}++;
        }
    }

## Update terminal in nodes
    foreach my $node ( @{ $machine->{node} } ) {

## Get terminal from node
        my $terminal = $self->{Process}->{ASF}->get_node_terminal($node);

## Goto next node
        next if !$terminal or !$terminal->{id};

## Parse terminal from ACF
        my $default_terminal =
          $self->{Process}->{ACF}->get_terminal( $terminal->{id} );

## Goto next node, If default terminal for node not specified
        next if !$default_terminal;

## Update terminal Port
        if ( !$terminal->{port} and $default_terminal->{port} ) {

## Set port
            $terminal->{port} = $default_terminal->{port};

## Set machine updated state
            $self->{machine_updated}++;
        }

## Update terminal timeout
        if ( !$terminal->{timeout} and $default_terminal->{timeout} ) {

## Set timeout
            $terminal->{timeout} = $default_terminal->{timeout};

## Set machine updated state
            $self->{machine_updated}++;
        }

## Update terminal idle
        if ( !$terminal->{idle} and $default_terminal->{idle} ) {

## Set idle
            $terminal->{idle} = $default_terminal->{idle};

## Set machine updated state
            $self->{machine_updated}++;
        }

## Update terminal Whoami
        if (    !$terminal->{whoami}
            and !$terminal->{whoami}->[0]->{content}
            and $default_terminal->{whoami}
            and $default_terminal->{whoami}->[0]->{content} )
        {

## Set whoami
            $terminal->{whoami}->[0]->{content} =
              $default_terminal->{whoami}->[0]->{content};

## Set machine updated state
            $self->{machine_updated}++;
        }
        elsif ( !$terminal->{whoami}->[0]->{content} ) {
            delete( $terminal->{whoami} );
        }

## Update terminal Separator
        if (    !$terminal->{separator}
            and !$terminal->{separator}->[0]->{content}
            and $default_terminal->{separator}
            and $default_terminal->{separator}->[0]->{content} )
        {

## Set separator
            $terminal->{separator}->[0]->{content} =
              $default_terminal->{separator}->[0]->{content};

## Set machine updated state
            $self->{machine_updated}++;
        }
        elsif ( !$terminal->{separator}->[0]->{content} ) {
            delete( $terminal->{separator} );
        }
    }

## Open Keyfile
    my $cbc = $self->{Process}->{ACF}->open_keyfile();

## If Keyfile not opened
    if ( !$cbc ) {

## Print Keyfile not found or encription broken message
        $self->{Process}->{ACF}->message_failed_passwd();
    }

## Check passwd for unit in ACF
    foreach my $passwd ( @{ $self->{Process}->{ACF}->get_list_passwd() } ) {

## Skip wrong passwd
        next
          if !$passwd->{unit}
              or $passwd->{unit} ne $self->get_machine_unit($node_id);

## Check if site's passwd defined
        my $site_passwd =
          $self->{Process}->{ACF}
          ->get_site_passwd( $passwd->{login}, $passwd->{unit} );

## If password in site's ACF defined
        if ( $site_passwd and $site_passwd->{content} ) {
            $passwd->{content}   = $site_passwd->{content};
            $passwd->{encrypted} = $site_passwd->{encrypted};
        }

## Skip empty password
        next if !$passwd->{content};

## Check if passwd already in ASF's machine
        my $mach_passwd =
          $self->get_machine_passwd( $node_id, $passwd->{login} );

## If password in ASF not defined
        if ( !$mach_passwd ) {

## Add machine password
            $self->add_machine_passwd( $node_id, $passwd );

## Goto next password
            next;
        }

## Goto next password if defined in ASF
        next if $mach_passwd->{content};

## Check password for all nodes
        foreach my $session_node ( @{ $machine->{node} } ) {

## Set password from ACF
            $mach_passwd->{content}   = $passwd->{content};
            $mach_passwd->{encrypted} = $passwd->{encrypted};

## Create new session
            my $session =
              new AFX::Session( $self, $passwd->{login}, $session_node );

## Exception: No connection available to host:terminal as login
            if ( !$session ) {

## Add step's static into ARF step
                $self->message_failed_session( $session->{id} );

## Set empty password
                delete( $mach_passwd->{content} );
                delete( $mach_passwd->{encrypted} );

## Goto next password
                next;
            }    ## Exception

## If session not available and no errors
            if ( !$session->{_session} and !$session->{_error} ) {

## Try to open session
                $session->open_session();
            }

## Last if session opened
            last if $session->{_session};

## Try to close session
            $session->close_session();

## Set empty password
            delete( $mach_passwd->{content} );
            delete( $mach_passwd->{encrypted} );
        }

## Next if password's content deleted before
        next if !$mach_passwd->{content};

## Print Password encrypted message
        $self->message_passwd_encrypted( $passwd->{login} );

## Set updated flag
        $self->{updated}++;

## Set machine updated flag
        $self->{machine_updated}++;
    }

## Check passwd in Machine
    foreach my $passwd ( @{ $machine->{passwd} } ) {

## Skip if password not encrypted
        next if !$passwd->{encrypted} or $passwd->{encrypted} eq "true";

## Skip if cbc not initialized
        next if !$cbc;

## Encrypt password
        $passwd->{content} = $cbc->encrypt_hex( $passwd->{content} );

## Set encrypted flag to true
        $passwd->{encrypted} = 'true';

## Print Password encrypted message
        $self->message_passwd_encrypted( $passwd->{login} );

## Set ARF updated flag
        $self->{updated}++;

## Set machine updated flag
        $self->{machine_updated}++;
    }

## Set updated state
    $self->{updated}++;

## Return Ok
    return 1;

} ## end sub mastering_machine

##############################################################################
#
# message_argument_added()
#
# Message: Argument added into machine in ASF.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - argument_id  - Argument's Id

sub message_argument_added {
    my ( $self, $argument_id ) = @_;

## Set message
    my $message = "$self->{id}: argument $argument_id added";

## Return message
    return $self->{Process}->{ACF}->logging( $message, 0, 'audit' );

} ## end sub message_argument_added

##############################################################################
#
# message_atf_processed()
#
# Message: Template Completed for Machine
#
# Return:
# - logged message
#
# Arguments expected are:
# - self          - Object reference
# - template_id   - Template's Id

sub message_atf_processed {
    my ( $self, $template_id ) = @_;

## Set message
    my $message = "$self->{id}: ATF $template_id processed";

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_atf_processed

##############################################################################
#
# message_created()
#
# Message: ARF created
#
# Return:
# - logged message
#
# Arguments expected are:
# - self        - Object reference

sub message_created {
    my ($self) = @_;

## Set message
    my $message = "ARF $self->{file}: created successfully";

## Return message
    return $self->{Process}->{ACF}->logging( $message, 0, 'audit' );

} ## end sub message_created

##############################################################################
#
# message_critical()
#
# Message: Critical issue(s) detected
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference

sub message_critical {
    my ($self) = @_;

## Set message
    my $message = "Critical issue(s) detected. ";
    $message .=
      "Audit won't be accepted for review till critical issue(s) addressed.";

## Return Message, if error already added into ARF
    return $message if !$self->add_error($message);

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_critical

##############################################################################
#
# message_failed_open()
#
# Message: ARF failed opening
#
# Return:
# - logged message
#
# Arguments expected are:
# - self        - Object reference
# - error       - Error

sub message_failed_open {
    my ( $self, $error ) = @_;

## Set message
    my $message = "$self->{id}: failed opening.";
    $message .= "\nError: $error" if $error;

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_failed_open

##############################################################################
#
# message_failed_session()
#
# Message: Session closing failed
#
# Return:
# - logged message
#
# Arguments expected are:
# - self          - Object reference
# - session_id    - Session's Id

sub message_failed_session {
    my ( $self, $session_id ) = @_;

## Set message
    my $message = "$self->{id}: failed session $session_id found";

## Return Message, if error already added into ARF
    return $message if !$self->add_error($message);

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_failed_session

##############################################################################
#
# message_failed_valid()
#
# Message: ARF failed validation
#
# Return:
# - logged message
#
# Arguments expected are:
# - self        - Object reference
# - error       - Error

sub message_failed_valid {
    my ( $self, $error ) = @_;

## Remove newlines in error
    $error =~ s/\n//g;

## Set message
    my $message = "ARF $self->{file}: failed validation.";
    $message .= "\nError: $error" if $error;

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_failed_valid

##############################################################################
#
# message_invalid_argument()
#
# Message: Argument has invalid content
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - node         - Node's Id
# - argument_id  - Argument's Identifier
# - step_id      - Step's Identifier

sub message_invalid_argument {
    my ( $self, $node_id, $argument_id, $step_id ) = @_;

## Set message
    my $message = "$node_id: invalid argument $step_id [$argument_id]";

## Return Message, if error already added into ARF
    return $message if !$self->add_error($message);

## Return message
    return $message;

} ## end sub message_invalid_argument

##############################################################################
#
# message_invalid_machine()
#
# Message: Invalid machine specified
#
# Return:
# - logged message
#
# Arguments expected are:
# - self        - Object reference
# - node_ip     - Node's IP
# - unit        - Machine's unit

sub message_invalid_machine {
    my ( $self, $node_ip, $unit ) = @_;

## Get Site's Id
    my $site_id = $self->get_site();

## Set message
    my $message =
"Machine [$unit] with node IP [$node_ip] doesn't exist in $site_id.master file. ";
    $message .= "Please add and rerun the audit.";

## Return Message, if error already added into ARF
    return $message if !$self->add_error($message);

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_machine

##############################################################################
#
# message_invalid_param()
#
# Message: Parameter has invalid content
#
# Return:
# - logged message
#
# Arguments expected are:
# - self        - Object reference
# - node_id     - Node's Id
# - remote_id   - Node's Id
# - param_id    - Parameter's Identifier
# - step_id     - Step's Identifier

sub message_invalid_param {
    my ( $self, $node_id, $remote_id, $param_id, $step_id ) = @_;

## Set message
    my $message = $node_id;
    $message .= "&" . $remote_id if $remote_id;
    $message .= ": invalid parameter $step_id [$param_id]";

## Return Message, if error already added into ARF
    return $message if !$self->add_error($message);

## Return message
    return $message;

} ## end sub message_invalid_param

##############################################################################
#
# message_invalid_passwd()
#
# Message: Invalid password specified.
#
# Return logged message
#
# Arguments expected are:
# - self        - Object reference
# - login       - Login

sub message_invalid_passwd {
    my ( $self, $login ) = @_;

## Set message
    my $message = "$self->{id}: password for $login not found.";
    $message .= "\nPlease specify in site.config file and rerun the audit.";

## Return Message, if error already added into ARF
    return $message if !$self->add_error($message);

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_passwd

##############################################################################
#
# message_invalid_refer()
#
# Message: Referrer should be specified for node
#
# Return:
# - logged message
#
# Arguments expected are:
# - self       - Object reference
# - node_id    - Node's Id
# - remote_id  - Remote Node's Id
# - step_id    - Step's Identifier

sub message_invalid_refer {
    my ( $self, $node_id, $remote_id, $step_id ) = @_;

## Set message
    my $message = $node_id;
    $message .= "&" . $remote_id if $remote_id;
    $message .= ": invalid referrer step $step_id ";

## Return Message, if error already added into ARF
    return $message if !$self->add_error($message);

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_refer

##############################################################################
#
# message_invalid_step()
#
# Message: Invalid step specified
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - step_id      - Step's Identifier
# - template_id  - Template's Identifier

sub message_invalid_step {
    my ( $self, $step_id, $template_id ) = @_;

## Set message
    my $message = "$self->{id}: invalid $template_id [$step_id] step found";

## Return Message, if error already added into ARF
    return $message if !$self->add_error($message);

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_invalid_step

##############################################################################
#
# message_no_hint()
#
# Message: Command or Param within Step has no hint
#
# Return:
# - logged message
#
# Arguments expected are:
# - self     - Object reference
# - atf_id   - ATF's Identifier
# - step_id  - Step's Identifier
# - param_id - Parameter's Identifier

sub message_no_hint {
    my ( $self, $atf_id, $step_id, $param_id ) = @_;

## Set message
    my $message = "No hint yet";
    $message .= " for $atf_id: step $step_id" if $atf_id and $step_id;
    $message .= " [$param_id]" if $param_id;

## Return message
    return $message;

} ## end sub message_no_hint

##############################################################################
#
# message_no_machine()
#
# Message: No machine found
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - step_id      - Step's Identifier
# - unit         - Machine's unit

sub message_no_machine {
    my ( $self, $step_id, $unit ) = @_;

## Set message
    my $message = "No machine [$unit] found for step $step_id";

## Return Message, if error already added into ARF
    return $message if !$self->add_error($message);

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_no_machine

##############################################################################
#
# message_no_param()
#
# Message: Hint within Step has no corresponding param
#
# Return:
# - logged message
#
# Arguments expected are:
# - self     - Object reference
# - atf_id   - ATF's Identifier
# - step_id  - Step's Identifier
# - hint_id  - Hint's Identifier

sub message_no_param {
    my ( $self, $atf_id, $step_id, $hint_id ) = @_;

## Set message
    my $message = "Invalid param's hint found";
    $message .= " in $atf_id: step $step_id" if $atf_id and $step_id;
    $message .= " [$hint_id]" if $hint_id;

## Return message
    return $message;

} ## end sub message_no_param

##############################################################################
#
# message_no_step()
#
# Message: No steps executed for machine
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference

sub message_no_step {
    my ($self) = @_;

## Set message
    my $message = "No steps executed for machine";

## Return Message, if error already added into ARF
    return $message if !$self->add_error($message);

## Return message
    return $message;

} ## end sub message_no_step

##############################################################################
#
# message_param_summary()
#
# ARF formatting parameters output
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - param_error  - Param hash

sub message_param_summary {
    my ( $self, $param ) = @_;

## Define null total parameters
    my $param_total = 0;

## Count total parameters
    if ($param) {
        $param_total = $param->{ok} + $param->{error} + $param->{na};
    }

## Set message
    my $message = "<b class=failed>Parameters summary: FAILED. ";
    $message .= "Resolve issues and rerun audit.</b>";

## If no error and ok parameter found
    if ( $param and !$param->{error} and $param->{ok} ) {

## Set message
        $message = "<b class=passed>Parameters summary: PASSED. ";
        $message .= "Anyway check it manually.</b>";
        $message .= "\nFailed: 0; ";
    }
    else {

## Set message
        $message .= "\n<b class=failed>Failed: $param->{error}</b>; ";
    }

## Add message content
    if ($param) {
        $message .= "Passed: $param->{ok}; ";
        $message .= "N/A: $param->{na}; Total: $param_total.";
    }

## Return message
    return $message;

} ## end sub message_param_summary

##############################################################################
#
# message_passwd_added()
#
# Message: Password added in ASF.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - login        - Login

sub message_passwd_added {
    my ( $self, $login ) = @_;

## Set message
    my $message = "$self->{id}: login $login added";

## Return message
    return $self->{Process}->{ACF}->logging( $message, 0, 'audit' );

} ## end sub message_passwd_added

##############################################################################
#
# message_passwd_encrypted()
#
# Message: Password for unit as login encrypted
#
# Return:
# - logged message
#
# Arguments expected are:
# - self       - Object reference
# - login      - Login

sub message_passwd_encrypted {
    my ( $self, $login ) = @_;

## Set message
    my $message = "$self->{id}: login $login encrypted";

## Return message
    return $self->{Process}->{ACF}->logging( $message, 0, 'audit' );

} ## end sub message_passwd_encrypted

##############################################################################
#
# message_processing()
#
# Message: ARF processing
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - tid          - Thread's Id

sub message_processing {
    my ( $self, $tid ) = @_;

## Set message
    my $message = ">>> $self->{id}: processing";
    $message .= " in thread" if $tid;

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_processing

##############################################################################
#
# message_processed()
#
# Message: Finished machine
#
# Return:
# - logged message
#
# Arguments expected are:
# - self        - Object reference
# - tid         - Thread's Id

sub message_processed {
    my ( $self, $tid ) = @_;

## Set message
    my $message = ">>> $self->{id}: processed successfully";
    $message .= " in thread" if $tid;

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_processed

##############################################################################
#
# message_step_processing()
#
# Message: Template's Step Processing for Machine
#
# Return:
# - logged message
#
# Arguments expected are:
# - self          - Object reference
# - step_id       - Step's Id

sub message_step_processing {
    my ( $self, $step_id ) = @_;

## Set message
    my $message = "$self->{id}: $step_id processing";

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_step_processing

##############################################################################
#
# message_template_added()
#
# Message: Template added in ASF.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - template_id  - Template's Identifier
# - machine_id   - Machine's Identifier

sub message_template_added {
    my ( $self, $template_id, $machine_id ) = @_;

## Set message
    my $message = "$self->{id}: ATF $template_id validated on $machine_id";

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_template_added

##############################################################################
#
# message_template_removed()
#
# Message: Template removed in ASF.
#
# Return:
# - logged message
#
# Arguments expected are:
# - self         - Object reference
# - template_id  - Template's Id

sub message_template_removed {
    my ( $self, $template_id ) = @_;

## Set message
    my $message = "$self->{id}: ATF $template_id removed!!!";

## Return message
    return $self->{Process}->{ACF}->logging( $message, 1, 'audit' );

} ## end sub message_template_removed

##############################################################################
#
# new()
#
# ARF contructor
#
# Arguments expected are:
# - Process's Object
# - Machine's Hash
# - Process's Identifier
# - Scenario's Identifier

sub new {

## Get package name
    my $type = shift;

## Define object's hash
    my $self = { Process => shift, updated => 0, machine_updated => 0 };

## Get machine
    my $machine = shift;

## Get process's id
    my $process_id = shift;

## Get scenario's id
    $self->{scenario} = shift;

## Set empty report
    $self->{report} = "";

## Set ARF identifier
    $self->{id} = $self->{Process}->get_site();

## If daemon mode evoked
    if ( $self->{Process}->{_xml}->{daemon} or $process_id eq 'daemon' ) {

## Set process as daemon
        $process_id = 'daemon' if $process_id ne 'daemon';

## Set ARF's Id
        $self->{id} .= "_$machine->{id}" . "_daemon";
    }

## If process audit
    if ( $process_id eq 'audit' ) {

## Set ARF's report Id
        $self->{report} = $self->{id} . "_$machine->{id}" . "_report";

## Set ARF's Id
        $self->{id} .= "_$machine->{id}" . "_audit";
    }

## If process scenario
    if ( $process_id eq 'scenario' and $self->{scenario} ) {

## Set today as date+time
        my $today = strftime "%y%m%d_%H%M", localtime;

## Set ARF's report Id
        $self->{report} =
            $self->{id}
          . "_$machine->{id}" . "_"
          . $self->{scenario} . "_"
          . $today
          . "_report";

## Set ARF identifier
        $self->{id} .=
          "_$machine->{id}" . "_" . $self->{scenario} . "_" . $today;
    }

## If process rcmd
    if ( $process_id eq 'rcmd' ) {

## Set today as date+time
        my $today = strftime "%y%m%d_%H%M", localtime;

## Set ARF identifier
        $self->{id} .= "_$process_id" . "_" . $today;
    }

## Set ARF file
    $self->{file} = $self->{id} . ".xml";

## Get directory
    $self->{directory} = $self->{Process}->{ACF}->get_directory('current');

## Set full pathname
    $self->{path} = $self->{directory} . "/" . $self->{file};

## Return Error, if process invalid
    return 0 if !$self->{Process};

## Set ARF hash
    $self->{_xml} = {
        site    => $self->{Process}->get_site(),
        id      => $process_id,
        start   => time(),
        end     => time(),
        version => $self->{Process}->{ACF}->get_version(),
        step    => []
    };

## Add machine into ARF
    push( @{ $self->{_xml}->{machine} }, dclone($machine) );

## Add process into ARF
    push( @{ $self->{_xml}->{process} }, dclone( $self->{Process}->{_xml} ) );

## Get first node in initial Machine
    $self->{_node} = $self->{_xml}->{machine}->[0]->{node}->[0];

## Get first process in ARF
    $self->{_process} = $self->{_xml}->{process}->[0];

## Set empty sessions
    $self->{_sessions} = ();

## Set updated state
    $self->{updated}++;

## Print debug message
    print "DEBUG::Object ARF " . $self->{id} . " created\n"
      if $self->{Process}->is_debug();

## Return object
    return bless $self, $type;

} ## end sub new

##############################################################################
#
# open()
#
# Open ARF
#
# Return:
# - 1             - if opened successfully
# - 0             - otherwise
#
# Arguments expected are:
# - self          - Object reference

sub open {
    my ($self) = @_;

## If failed opening
    if ( !-f $self->{path} ) {

## Print failed openning error
        $self->message_failed_open();

## Return error
        return 0;
    }

## Validate ASF
    my $error = $self->{Process}->{ACF}->validate_file( $self->{path} );

## Exit if ARF failed validation
    if ($error) {

## Print failed validation error
        $self->message_failed_valid($error);

## Return Error
        return 0;
    }

## Open ARF
    $self->{_xml} =
      eval { $self->{Process}->{ACF}->{xs}->XMLin( $self->{path} ); };

## Exit if ARF failed opening
    if ( $@ or !$self->{_xml} or !$self->{_xml}->{id} ) {

## Print failed openning error
        $self->message_failed_open("$@");

## Return Error
        return 0;
    }

## Get first node in initial Machine
    $self->{_node} = $self->{_xml}->{machine}->[0]->{node}->[0];

## Get first process in ARF
    $self->{_process} = $self->{_xml}->{process}->[0];

## Print debug message
    print "DEBUG::Object ARF " . $self->{id} . " opened\n"
      if $self->{Process}->is_debug();

## Return Ok
    return 1;

} ## end sub open

##############################################################################
#
# remove_session()
#
# Remove session
#
# Return:
# - 1               - if removed
# - 0               - otherwise
#
# Arguments expected are:
# - self            - Object reference
# - session_id      - Session's id

sub remove_session {
    my ( $self, $session_id ) = @_;

## Return Error, if no sessions opened
    return 0 if !$self->{_sessions} or !@{ $self->{_sessions} };

## Get sessions
    my @sessions = @{ $self->{_sessions} };

## Check opened session
    foreach my $i ( 0 .. $#{ $self->{_sessions} } ) {

## Skip wrong session
        next if $sessions[$i]->{id} ne $session_id;

## Delete session
        delete( $self->{_sessions}->[$i] );

## Return Ok
        return 1;
    }

## If session not found, return Error
    return 0;

} ## end sub remove_session

##############################################################################
#
# update()
#
# Update ARF
#
# Return:
# - 1         - if updated sucessfully
# - 0         - otherwise
#
# Arguments expected are:
# - self      - Object reference

sub update {
    my ($self) = @_;

## If ARF not Updated
    return 0 if !$self->{updated} and !$self->{machine_updated};

## Save ARF
    eval {
        $self->{Process}->{ACF}->{xs}->XMLout(
            $self->{_xml},
            RootName   => 'audit',
            OutputFile => $self->{path}
        );
    };

## Print debug message
    print "DEBUG::Object ARF " . $self->{id} . " updated\n\n"
      if $self->{Process}->is_debug();

## Validate ARF
    my $error = $self->{Process}->{ACF}->validate_file( $self->{path} );

## Exit if ARF failed validation
    if ($error) {

## Print failed validation error
        $self->message_failed_valid($error);

## Return Error
        return 0;
    }

## Empty update
    $self->{updated} = 0;

## Return Ok
    return 1;

} ## end sub update

1;
